在内核代码中常见到两个宏 likely() 和 unlikly() ,它们定义在 include/linux/compiler.h 中:
__builtin_expect 是 gcc 的内置函数,它的原型为 long __builtin_expect (long exp, long c) ,第 1 个参数可以是个整数,也可以是个布尔型表达式,函数的返回值为 exp 。使用 __builtin_expect 可以为编译器提供跳转预测信息,关于跳转预测相关内容可参考: http://www.groad.net/bbs/read.php?tid-1455-fpage-3.html 与 http://www.groad.net/bbs/read.php?tid-1456-fpage-3.html 。
该函数的意思是,期望 " exp == c " 。因此,
__builtin_expect(!!(x), 1) 表示 !!(x) 为真的可能性很大,如果 !!(x) 确实为真,那么整个函数的返回值为真。
__builtin_expect(!!(x), 0) 表示 !!(x) 为假的可能性很大,如果 !!(x) 确实为假,那么整个函数的返回值为假。
注意,上面的可能性是程序员所“认为”的,但实际情况不一定就是真或假,然而编译器会按照你所认为的 “可能性” 去安排跳转预测。
下面通过测试程序来验证:
反汇编:
从上面可以看到两个反汇编的内容一样,也就是说不管传递的参数是真或是假,程序跳转预测的安排都只是根据 __builtin_expect 来安排。
因为 unlikely() 是条件很可能为假,那么也就是要执行 x = x + 10 这条语句,所以编译器不安排跳转。从指令缓存的角度来看,跳转很可能是跳转到缓存之外的,这样会减慢程序的执行速度。
同样可以测试 likely() 的情况。
另外需要注意一点区别,unlikely() 用的是 jne 指令,而 likely() 用的是 je 指令。
引用
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
__builtin_expect 是 gcc 的内置函数,它的原型为 long __builtin_expect (long exp, long c) ,第 1 个参数可以是个整数,也可以是个布尔型表达式,函数的返回值为 exp 。使用 __builtin_expect 可以为编译器提供跳转预测信息,关于跳转预测相关内容可参考: http://www.groad.net/bbs/read.php?tid-1455-fpage-3.html 与 http://www.groad.net/bbs/read.php?tid-1456-fpage-3.html 。
该函数的意思是,期望 " exp == c " 。因此,
__builtin_expect(!!(x), 1) 表示 !!(x) 为真的可能性很大,如果 !!(x) 确实为真,那么整个函数的返回值为真。
__builtin_expect(!!(x), 0) 表示 !!(x) 为假的可能性很大,如果 !!(x) 确实为假,那么整个函数的返回值为假。
注意,上面的可能性是程序员所“认为”的,但实际情况不一定就是真或假,然而编译器会按照你所认为的 “可能性” 去安排跳转预测。
下面通过测试程序来验证:
编译:#include <stdio.h> #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) void test_like (int x) { if (unlikely(x)) x = x + 8; else x = x + 10; printf ("x = %d\n", x); } int main(void) { test_like (10); return (0); }
引用gcc -O2 builtin_expect.c -o builtin_expect
反汇编:
将上面程序中的 test_like(10) 中的 10 改为 0 再看反汇编:08048430 <test_like>: 8048430: 55 push %ebp 8048431: 89 e5 mov %esp,%ebp 8048433: 83 ec 18 sub $0x18,%esp 8048436: 8b 45 08 mov 0x8(%ebp),%eax 8048439: 85 c0 test %eax,%eax 804843b: 75 17 jne 8048454 <test_like+0x24> 804843d: b8 0a 00 00 00 mov $0xa,%eax /* x = x + 10; */ 8048442: 89 44 24 04 mov %eax,0x4(%esp) 8048446: c7 04 24 50 85 04 08 movl $0x8048550,(%esp) 804844d: e8 02 ff ff ff call 8048354 <printf@plt> 8048452: c9 leave 8048453: c3 ret 8048454: 83 c0 08 add $0x8,%eax /* x = x + 8 */ 8048457: eb e9 jmp 8048442 <test_like+0x12> 8048459: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
08048430 <test_like>: 8048430: 55 push %ebp 8048431: 89 e5 mov %esp,%ebp 8048433: 83 ec 18 sub $0x18,%esp 8048436: 8b 45 08 mov 0x8(%ebp),%eax 8048439: 85 c0 test %eax,%eax 804843b: 75 17 jne 8048454 <test_like+0x24> 804843d: b8 0a 00 00 00 mov $0xa,%eax 8048442: 89 44 24 04 mov %eax,0x4(%esp) 8048446: c7 04 24 50 85 04 08 movl $0x8048550,(%esp) 804844d: e8 02 ff ff ff call 8048354 <printf@plt> 8048452: c9 leave 8048453: c3 ret 8048454: 83 c0 08 add $0x8,%eax 8048457: eb e9 jmp 8048442 <test_like+0x12> 8048459: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
从上面可以看到两个反汇编的内容一样,也就是说不管传递的参数是真或是假,程序跳转预测的安排都只是根据 __builtin_expect 来安排。
因为 unlikely() 是条件很可能为假,那么也就是要执行 x = x + 10 这条语句,所以编译器不安排跳转。从指令缓存的角度来看,跳转很可能是跳转到缓存之外的,这样会减慢程序的执行速度。
同样可以测试 likely() 的情况。
另外需要注意一点区别,unlikely() 用的是 jne 指令,而 likely() 用的是 je 指令。
本文深入探讨了Linux内核代码中常用的likely()和unlikely()宏的作用和实现原理,解释了如何利用这些宏进行跳转预测优化,并通过测试程序验证其效果。

322

被折叠的 条评论
为什么被折叠?



