Search code examples
c++assemblygccc++20likely-unlikely

Simple example where [[likely]] and [[unlikely]] affect program assembly?


C++20 introduced the attributes [[likely]] and [[unlikely]] to the language, which can be used to allow the compiler to optimize for the case where one execution path is either much more likely or much less likely than the other ones.

Given the cost of an incorrect branch prediction, this seems like a feature that's potentially extremely useful in performance-critical sections of code, but I don't know what it would actually cause the compiler to do.

Is there a simple piece of code for which adding [[likely]] and [[unlikely]] attributes changes the assembly output by the compiler? And perhaps more importantly, what do these changes do?

I created a simple example for my own understanding to see if there was any difference in the assembly, but it appears that this example is too simple to actually show any changes to the assembly:

void true_path();
void false_path();

void foo(int i) {
    if(i) {
        true_path();
    } else {
        false_path();
    }
}
void bar(int i) {
    if(i) [[likely]] {
        true_path();
    } else [[unlikely]] {
        false_path();
    }
}

View the compiled assembly here.


Solution

  • As it seems, there is a bug in gcc. If you have two functions which are the same, besides [[likely]] attributes, gcc folds them incorrectly.

    But if you use just one function, and switch between [[likely]]/[[unlikely]], assembly changes.

    So, this function:

    void bar(int i) {
        if(i) [[unlikely]] {
            true_path();
        } else [[likely]] {
            false_path();
        }
    }
    

    compiles to:

    bar(int):
            test    edi, edi
            jne     .L4
            jmp     false_path()
    .L4:
            jmp     true_path()
    

    And this:

    void bar(int i) {
        if(i) [[likely]] {
            true_path();
        } else [[unlikely]] {
            false_path();
        }
    }
    

    compiles to:

    bar(int):
            test    edi, edi
            je      .L2
            jmp     true_path()
    .L2:
            jmp     false_path()
    

    Notice, that the condition has changed: the first version jumps, if i is non-zero, while the second one jumps if i is zero.

    This is in agreement with the attributes: gcc generates code, where the conditional jump happens in the unlikely path.