c++c++20likely-unlikely

Do likelihood attributes make sense with a single if statement?


Cppreference and this documentation do not state explicitly that likelihood attributes won't work with a single if statement. Or, I just do not understand what is meant by alternative path of execution. So that's my question, will the attribute, say, [[unlikely]], work in the case below?

if (condition) [[unlikely]] {
    do_stuff();
}

Solution

  • Yes, it makes sense. The alternative, [[likely]], path is the one where the condition is false, that is, the path not calling do_stuff();. That becomes the path it'll try to optimize for.

    Example:

    #include <iostream>
    
    inline void do_stuff() {
        std::cout << "Surprise!\n";
    }
    
    int main(int argc, char**) {
        if (argc == 0) [[likely]] {
            do_stuff();
        }
    }
    

    Assembler with [[likely]]:

    .LC0:
            .string "Surprise!\n"
    main:
            test    edi, edi
            jne     .L4
            sub     rsp, 8
            mov     edx, 10
            mov     esi, OFFSET FLAT:.LC0
            mov     edi, OFFSET FLAT:_ZSt4cout
            call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
            xor     eax, eax
            add     rsp, 8
            ret
    .L4:
            xor     eax, eax
            ret
    _GLOBAL__sub_I_main:
            sub     rsp, 8
            mov     edi, OFFSET FLAT:_ZStL8__ioinit
            call    std::ios_base::Init::Init() [complete object constructor]
            mov     edx, OFFSET FLAT:__dso_handle
            mov     esi, OFFSET FLAT:_ZStL8__ioinit
            mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
            add     rsp, 8
            jmp     __cxa_atexit
    

    With [[unlikely]] (and without attribute at all):

    .LC0:
            .string "Surprise!\n"
    main:
            test    edi, edi
            je      .L8
            xor     eax, eax
            ret
    .L8:
            push    rax
            mov     edx, 10
            mov     esi, OFFSET FLAT:.LC0
            mov     edi, OFFSET FLAT:_ZSt4cout
            call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
            xor     eax, eax
            pop     rdx
            ret
    _GLOBAL__sub_I_main:
            sub     rsp, 8
            mov     edi, OFFSET FLAT:_ZStL8__ioinit
            call    std::ios_base::Init::Init() [complete object constructor]
            mov     edx, OFFSET FLAT:__dso_handle
            mov     esi, OFFSET FLAT:_ZStL8__ioinit
            mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
            add     rsp, 8
            jmp     __cxa_atexit
    

    Those are two slightly different outcomes and without knowing too much about assembler, I'd say the effect of putting [[likely]] there is clear. It looks to me that putting [[likely]] there made it inline the function while [[unlikely]] left it as a function call.