Using probable () / unlikely () preprocessor macros in if-else if chain

If I have:

#define likely(x) __builtin_expect((x),1) #define unlikely(x) __builtin_expect((x),0) if (A) return true; else if (B) return false; ... else if (Z) return true; else //this will never really happen!!!! raiseError(); return false; 

Is it possible to put probable () around the last condition, for example, as else if (likely(Z)) to mean that the final operator (else) is very unlikely, NOT the compiler affecting the prediction of branches of previous checks?

Basically, does GCC try to optimize the whole if-else if block if there is one conditional statement with a branch predictor hint?

+6
source share
2 answers

You must make this explicit:

 if (A) return true; else if (B) return true; ... else if (Y) return true; else { if (likely(Z)) return true; raiseError(); return false; } 

Now the compiler clearly understands your intent and does not reassign other branching probabilities. Code readability has also improved.

PS I suggest you rewrite it also, probably, and it is unlikely that the Linux kernel does this to protect against noiseless integrated responses:

 #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) 
+8
source

In general, GCC assumes that conditional expressions in if statements are true - there are exceptions, but they are contextual.

 extern int s(int); int f(int i) { if (i == 0) return 1; return s(i); } 

produces

 f(int): testl %edi, %edi jne .L4 movl $1, %eax ret .L4: jmp s(int) 

a

 extern int t(int*); int g(int* ip) { if (!ip) return 0; return t(ip); } 

gives:

 g(int*): testq %rdi, %rdi je .L6 jmp t(int*) .L6: xorl %eax, %eax ret 

(see godbolt )

Note that in f the jne branch (suppose the condition is true), and in g condition is considered false.

Now compare with the following:

 extern int s(int); extern int t(int*); int x(int i, int* ip) { if (!ip) return 1; if (!i) return 2; if (s(i)) return 3; if (t(ip)) return 4; return s(t(ip)); } 

which produces

 x(int, int*): testq %rsi, %rsi je .L3 # first branch: assumed unlikely movl $2, %eax testl %edi, %edi jne .L12 # second branch: assumed likely ret .L12: pushq %rbx movq %rsi, %rbx call s(int) movl %eax, %edx movl $3, %eax testl %edx, %edx je .L13 # third branch: assumed likely .L2: popq %rbx ret .L3: movl $1, %eax ret .L13: movq %rbx, %rdi call t(int*) movl %eax, %edx movl $4, %eax testl %edx, %edx jne .L2 # fourth branch: assumed unlikely! movq %rbx, %rdi call t(int*) popq %rbx movl %eax, %edi jmp s(int) 

Here we see one of the contextual factors: GCC noticed that he could reuse L2 here, so he decided to consider the final conditional unlikely so that he could emit less code.

Let's look at the assembly of the example you specified:

 #define likely(x) __builtin_expect((x),1) #define unlikely(x) __builtin_expect((x),0) extern void raiseError(); int f(int A, int B, int Z) { if (A) return 1; else if (B) return 2; else if (Z) return 3; raiseError(); return -1; } 

The assembly is as follows:

 f(int, int, int): movl $1, %eax testl %edi, %edi jne .L9 movl $2, %eax testl %esi, %esi je .L11 .L9: ret .L11: testl %edx, %edx je .L12 # branch if !Z movl $3, %eax ret .L12: subq $8, %rsp call raiseError() movl $-1, %eax addq $8, %rsp ret 

Note that the generated code branches when! Z is true, he is already behaving as if Z was probable. What happens if we say that Z is likely?

 #define likely(x) __builtin_expect((x),1) #define unlikely(x) __builtin_expect((x),0) extern void raiseError(); int f(int A, int B, int Z) { if (A) return 1; else if (B) return 2; else if (likely(Z)) return 3; raiseError(); return -1; } 

now we get

 f(int, int, int): movl $1, %eax testl %edi, %edi jne .L9 movl $2, %eax testl %esi, %esi je .L11 .L9: ret .L11: movl $3, %eax # assume Z testl %edx, %edx jne .L9 # but branch if Z subq $8, %rsp call raiseError() movl $-1, %eax addq $8, %rsp ret 

The point here is that you should be careful when using these macros and carefully study the code before and after a thorough check to make sure that you get the expected results, and a comparative test (for example, with performance) to make sure that the processor makes predictions that are consistent with the code you generate.

+2
source

All Articles