likely && unlikely in GCC

在linux内核源码或一些比较成熟的c语言架构源码中,我们常会见到类似下面的代码:

1 if (unlikely(!packet)) {
2     return res_failed;   
3 }
4 
5 // OR
6 
7 if (likely(packet->type = HTTP)) {
8     do_something();
9 }

有的地方可能会用大写,LIKELY() / UNLIKELY(),意思一样。

然后我们看一下unlikely和likely的定义,大部分都是类似如下的宏定义:

1 #define likely(x)   __builtin_expect(!!(x), 1)  
2 #define unlikely(x) __builtin_expect(!!(x), 0)  

GCC 中用的是 _G_BOOLEAN_EXPR(expr) 来代替 !!(expr), 意思一样,都是把expr或x转成相应布尔变量。

两个定义无一例外调用了一个内置函数 __builtin_expect(bool expr,  int x)。

先解释一下:  LIKELY 和 UNLIKELY 不会对原expr的布尔值产生任何影响,也就是说只要expr == true, LIKELY(expr) 与 UNLIKELY(expr) 都为 true。他们起的只是编译器优化作用。

我们先测试一段代码:

 1 /**
 2  * @author Lhfcws
 3  * @file test__builtin_expect.c 
 4  * @time 2013-07-22
 5  **/
 6 
 7 #define LIKELY(x) __builtin_expect(!!(x), 1)
 8 #define UNLIKELY(x) __builtin_expect(!!(x), 0)
 9 
10 int test_likely(int x) {
11     if(LIKELY(x))
12         x = 0x00;
13     else
14         x = 0xff;
15 
16     return x;
17 }
18 
19 int test_unlikely(int x) {
20     if(UNLIKELY(x))
21         x = 0x00;
22     else
23         x = 0xff;
24 
25     return x;
26 }
27 
28 int test_justif(int x) {
29     if(x)
30         x = 0x00;
31     else
32         x = 0xff;
33 
34     return x;
35 }

可见,三个函数唯一的区别就是 if (x) 那里。

我们执行一下命令编译和反汇编(要用 __builtin_expect 的话  -fprofile-arcs 必须加):

gcc -fprofile-arcs -c test__builtin_expect.c -o test__builtin_expect.o
objdump -d test__builtin_expect > builtin.asm

此时打开生成的asm文件,就可以看到上面代码由gcc编译生成的汇编代码。我们截取那三个函数的汇编源码查看。

 1 00000000 <test_likely>:
 2    0:    55                       push   %ebp
 3    1:    89 e5                    mov    %esp,%ebp
 4    3:    83 7d 08 00              cmpl   $0x0,0x8(%ebp)
 5    7:    0f 95 c0                 setne  %al
 6    a:    0f b6 c0                 movzbl %al,%eax
 7    d:    85 c0                    test   %eax,%eax
 8    f:    74 09                    je     1a <test_likely+0x1a>
 9   11:    c7 45 08 00 00 00 00     movl   $0x0,0x8(%ebp)
10   18:    eb 23                    jmp    3d <test_likely+0x3d>
11   1a:    c7 45 08 ff 00 00 00     movl   $0xff,0x8(%ebp)
12   21:    a1 20 00 00 00           mov    0x20,%eax
13   26:    8b 15 24 00 00 00        mov    0x24,%edx
14   2c:    83 c0 01                 add    $0x1,%eax
15   2f:    83 d2 00                 adc    $0x0,%edx
16   32:    a3 20 00 00 00           mov    %eax,0x20
17   37:    89 15 24 00 00 00        mov    %edx,0x24
18   3d:    8b 4d 08                 mov    0x8(%ebp),%ecx
19   40:    a1 28 00 00 00           mov    0x28,%eax
20   45:    8b 15 2c 00 00 00        mov    0x2c,%edx
21   4b:    83 c0 01                 add    $0x1,%eax
22   4e:    83 d2 00                 adc    $0x0,%edx
23   51:    a3 28 00 00 00           mov    %eax,0x28
24   56:    89 15 2c 00 00 00        mov    %edx,0x2c
25   5c:    89 c8                    mov    %ecx,%eax
26   5e:    5d                       pop    %ebp
27   5f:    c3                       ret    
28 
29 00000060 <test_unlikely>:
30   60:    55                       push   %ebp
31   61:    89 e5                    mov    %esp,%ebp
32   63:    83 7d 08 00              cmpl   $0x0,0x8(%ebp)
33   67:    0f 95 c0                 setne  %al
34   6a:    0f b6 c0                 movzbl %al,%eax
35   6d:    85 c0                    test   %eax,%eax
36   6f:    74 09                    je     7a <test_unlikely+0x1a>
37   71:    c7 45 08 00 00 00 00     movl   $0x0,0x8(%ebp)
38   78:    eb 23                    jmp    9d <test_unlikely+0x3d>
39   7a:    c7 45 08 ff 00 00 00     movl   $0xff,0x8(%ebp)
40   81:    a1 10 00 00 00           mov    0x10,%eax
41   86:    8b 15 14 00 00 00        mov    0x14,%edx
42   8c:    83 c0 01                 add    $0x1,%eax
43   8f:    83 d2 00                 adc    $0x0,%edx
44   92:    a3 10 00 00 00           mov    %eax,0x10
45   97:    89 15 14 00 00 00        mov    %edx,0x14
46   9d:    8b 4d 08                 mov    0x8(%ebp),%ecx
47   a0:    a1 18 00 00 00           mov    0x18,%eax
48   a5:    8b 15 1c 00 00 00        mov    0x1c,%edx
49   ab:    83 c0 01                 add    $0x1,%eax
50   ae:    83 d2 00                 adc    $0x0,%edx
51   b1:    a3 18 00 00 00           mov    %eax,0x18
52   b6:    89 15 1c 00 00 00        mov    %edx,0x1c
53   bc:    89 c8                    mov    %ecx,%eax
54   be:    5d                       pop    %ebp
55   bf:    c3                       ret    
56 
57 000000c0 <test_justif>:
58   c0:    55                       push   %ebp
59   c1:    89 e5                    mov    %esp,%ebp
60   c3:    83 7d 08 00              cmpl   $0x0,0x8(%ebp)
61   c7:    74 09                    je     d2 <test_justif+0x12>
62   c9:    c7 45 08 00 00 00 00     movl   $0x0,0x8(%ebp)
63   d0:    eb 23                    jmp    f5 <test_justif+0x35>
64   d2:    c7 45 08 ff 00 00 00     movl   $0xff,0x8(%ebp)
65   d9:    a1 00 00 00 00           mov    0x0,%eax
66   de:    8b 15 04 00 00 00        mov    0x4,%edx
67   e4:    83 c0 01                 add    $0x1,%eax
68   e7:    83 d2 00                 adc    $0x0,%edx
69   ea:    a3 00 00 00 00           mov    %eax,0x0
70   ef:    89 15 04 00 00 00        mov    %edx,0x4
71   f5:    8b 4d 08                 mov    0x8(%ebp),%ecx
72   f8:    a1 08 00 00 00           mov    0x8,%eax
73   fd:    8b 15 0c 00 00 00        mov    0xc,%edx
74  103:    83 c0 01                 add    $0x1,%eax
75  106:    83 d2 00                 adc    $0x0,%edx
76  109:    a3 08 00 00 00           mov    %eax,0x8
77  10e:    89 15 0c 00 00 00        mov    %edx,0xc
78  114:    89 c8                    mov    %ecx,%eax
79  116:    5d                       pop    %ebp
80  117:    c3                       ret    

如上,我们看到,貌似test_likely 和 test_unlikely 没什么区别, test_justif就是少了setne al开始的三行代码而已(实际上是执行__builtin_expect(!!(x), 1)的代码)。

其实这证明了一件事: LIKELY 和 UNLIKELY 的调用不会影响最终结果,实际两者的结果是一样的。

我们之前提到他们起的作用是优化,因此我们编译的时候加上优化指令。

gcc -O2 -fprofile-arcs -c test__builtin_expect.c -o test__builtin_expect.o
objdump -d test__builtin_expect > builtin_O2.asm

得到汇编:

 1 00000000 <test_likely>:
 2    0:    83 ec 04                 sub    $0x4,%esp
 3    3:    8b 44 24 08              mov    0x8(%esp),%eax
 4    7:    83 05 00 00 00 00 01     addl   $0x1,0x0
 5    e:    83 15 04 00 00 00 00     adcl   $0x0,0x4
 6   15:    85 c0                    test   %eax,%eax
 7   17:    74 06                    je     1f <test_likely+0x1f>
 8   19:    31 c0                    xor    %eax,%eax
 9   1b:    83 c4 04                 add    $0x4,%esp
10   1e:    c3                       ret    
11   1f:    83 05 08 00 00 00 01     addl   $0x1,0x8
12   26:    b8 ff 00 00 00           mov    $0xff,%eax
13   2b:    83 15 0c 00 00 00 00     adcl   $0x0,0xc
14   32:    eb e7                    jmp    1b <test_likely+0x1b>
15   34:    8d b6 00 00 00 00        lea    0x0(%esi),%esi
16   3a:    8d bf 00 00 00 00        lea    0x0(%edi),%edi
17 
18 00000040 <test_unlikely>:
19   40:    83 ec 04                 sub    $0x4,%esp
20   43:    8b 54 24 08              mov    0x8(%esp),%edx
21   47:    83 05 10 00 00 00 01     addl   $0x1,0x10
22   4e:    83 15 14 00 00 00 00     adcl   $0x0,0x14
23   55:    85 d2                    test   %edx,%edx
24   57:    75 17                    jne    70 <test_unlikely+0x30>
25   59:    83 05 18 00 00 00 01     addl   $0x1,0x18
26   60:    b8 ff 00 00 00           mov    $0xff,%eax
27   65:    83 15 1c 00 00 00 00     adcl   $0x0,0x1c
28   6c:    83 c4 04                 add    $0x4,%esp
29   6f:    c3                       ret    
30   70:    31 c0                    xor    %eax,%eax
31   72:    eb f8                    jmp    6c <test_unlikely+0x2c>
32   74:    8d b6 00 00 00 00        lea    0x0(%esi),%esi
33   7a:    8d bf 00 00 00 00        lea    0x0(%edi),%edi
34 
35 00000080 <test_justif>:
36   80:    83 ec 04                 sub    $0x4,%esp
37   83:    8b 4c 24 08              mov    0x8(%esp),%ecx
38   87:    83 05 20 00 00 00 01     addl   $0x1,0x20
39   8e:    83 15 24 00 00 00 00     adcl   $0x0,0x24
40   95:    31 c0                    xor    %eax,%eax
41   97:    85 c9                    test   %ecx,%ecx
42   99:    75 10                    jne    ab <test_justif+0x2b>
43   9b:    83 05 28 00 00 00 01     addl   $0x1,0x28
44   a2:    b0 ff                    mov    $0xff,%al
45   a4:    83 15 2c 00 00 00 00     adcl   $0x0,0x2c
46   ab:    83 c4 04                 add    $0x4,%esp
47   ae:    c3                       ret    

现在三个函数就有很明显的不同了。

留意一下每个函数其中的三行代码:

1 je ... / jne ...    ; 跳转
2 xor %eap, %eap      ; 其实是 mov 0x00, %eap,改用 xor 是编译器自己的优化。结果等价。
3 mov 0xff, %eap      ; x = 0xff

可以看到,likely版本和unlikely版本最大的区别是跳转的不同。

likely版本编译器会认为执行 x == true 的可能性比较大,因此将 x == false 的情况作为分支,减少跳转开销。

同理,unlikely版本编译器会认为执行 x == false 的可能性比较大,因此将 x == true 的情况作为分支,减少跳转开销。

总结:

likely 和 unlikely 的使用实际上是为了分支优化,不影响结果,据传,众多程序员平常很少注意分支优化情况,因此gcc有了这个选项。。。

unlikely 一般适用于(但不仅限于)一些错误检查,比如本文开头示例。likely适用于主分支场景,即根据期望大部分情况都会执行的场景。

原文地址:https://www.cnblogs.com/lhfcws/p/3205366.html