How do the likely/unlikely macros in the Linux kernel work and what is their benefit?

后端 未结 10 878
攒了一身酷
攒了一身酷 2020-11-22 12:58

I\'ve been digging through some parts of the Linux kernel, and found calls like this:

if (unlikely(fd < 0))
{
    /* Do something */
}

o

10条回答
  •  陌清茗
    陌清茗 (楼主)
    2020-11-22 13:03

    Let's decompile to see what GCC 4.8 does with it

    Without __builtin_expect

    #include "stdio.h"
    #include "time.h"
    
    int main() {
        /* Use time to prevent it from being optimized away. */
        int i = !time(NULL);
        if (i)
            printf("%d\n", i);
        puts("a");
        return 0;
    }
    

    Compile and decompile with GCC 4.8.2 x86_64 Linux:

    gcc -c -O3 -std=gnu11 main.c
    objdump -dr main.o
    

    Output:

    0000000000000000 
    : 0: 48 83 ec 08 sub $0x8,%rsp 4: 31 ff xor %edi,%edi 6: e8 00 00 00 00 callq b 7: R_X86_64_PC32 time-0x4 b: 48 85 c0 test %rax,%rax e: 75 14 jne 24 10: ba 01 00 00 00 mov $0x1,%edx 15: be 00 00 00 00 mov $0x0,%esi 16: R_X86_64_32 .rodata.str1.1 1a: bf 01 00 00 00 mov $0x1,%edi 1f: e8 00 00 00 00 callq 24 20: R_X86_64_PC32 __printf_chk-0x4 24: bf 00 00 00 00 mov $0x0,%edi 25: R_X86_64_32 .rodata.str1.1+0x4 29: e8 00 00 00 00 callq 2e 2a: R_X86_64_PC32 puts-0x4 2e: 31 c0 xor %eax,%eax 30: 48 83 c4 08 add $0x8,%rsp 34: c3 retq

    The instruction order in memory was unchanged: first the printf and then puts and the retq return.

    With __builtin_expect

    Now replace if (i) with:

    if (__builtin_expect(i, 0))
    

    and we get:

    0000000000000000 
    : 0: 48 83 ec 08 sub $0x8,%rsp 4: 31 ff xor %edi,%edi 6: e8 00 00 00 00 callq b 7: R_X86_64_PC32 time-0x4 b: 48 85 c0 test %rax,%rax e: 74 11 je 21 10: bf 00 00 00 00 mov $0x0,%edi 11: R_X86_64_32 .rodata.str1.1+0x4 15: e8 00 00 00 00 callq 1a 16: R_X86_64_PC32 puts-0x4 1a: 31 c0 xor %eax,%eax 1c: 48 83 c4 08 add $0x8,%rsp 20: c3 retq 21: ba 01 00 00 00 mov $0x1,%edx 26: be 00 00 00 00 mov $0x0,%esi 27: R_X86_64_32 .rodata.str1.1 2b: bf 01 00 00 00 mov $0x1,%edi 30: e8 00 00 00 00 callq 35 31: R_X86_64_PC32 __printf_chk-0x4 35: eb d9 jmp 10

    The printf (compiled to __printf_chk) was moved to the very end of the function, after puts and the return to improve branch prediction as mentioned by other answers.

    So it is basically the same as:

    int main() {
        int i = !time(NULL);
        if (i)
            goto printf;
    puts:
        puts("a");
        return 0;
    printf:
        printf("%d\n", i);
        goto puts;
    }
    

    This optimization was not done with -O0.

    But good luck on writing an example that runs faster with __builtin_expect than without, CPUs are really smart these days. My naive attempts are here.

    C++20 [[likely]] and [[unlikely]]

    C++20 has standardized those C++ built-ins: How to use C++20's likely/unlikely attribute in if-else statement They will likely (a pun!) do the same thing.

提交回复
热议问题