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();
}
CodePudding user response:
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.
CodePudding user response:
It makes sense and clang respects it nicely. Try compiling:
void do_stuff(void);
void unlikely(int condition){
if (condition) [[unlikely]] {
do_stuff();
}
}
void likely(int condition){
if (condition) [[likely]] {
do_stuff();
}
}
https://godbolt.org/z/hzahxzT3v
Clang at -O3 yields:
unlikely(int): # @unlikely(int)
test edi, edi
jne .LBB0_2
ret
.LBB0_2:
jmp do_stuff()@PLT # TAILCALL
likely(int): # @likely(int)
test edi, edi
je .LBB1_1
jmp do_stuff()@PLT # TAILCALL
.LBB1_1:
ret
Notice that both of these function tail-call optimize the call to do_stuff()
(by jmp
ing into it rather than calling it).
The difference is that with the unlikely case, control flows naturally into ret
(return instruction), branching only to jmp do_stuff()
if the condition is satisfied (jne
stands for jump not equal
meaning not equal to zero, i.e., jump on condition
).
With the likely case, on the other hand, control flows naturally into the jmp do_stuff()
, branching only to ret
if the reverse of the condition is satified (je
stands for jump equal
meaning equal to zero, i.e., jump on !condition
).
This is how it should be. x86_64 processors predict first encounters of a branch as not taken, so the optimal layout is for the expected paths to follow naturally and for the unexpected ones to be branched into.