![](/img/trans.png)
[英]How to use C++20's likely/unlikely attribute in if-else statement
[英]Using Likely() / Unlikely() Preprocessor Macros in if-else if chain
如果我有:
#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;
else if (likely(Z))
表示最終語句(else)非常不可能沒有編譯器影響先前檢查的分支預測,我可以將最近的條件檢查else if (likely(Z))
放在最后一個條件周圍嗎?
基本上,如果存在帶分支預測器提示的單個條件語句,GCC是否會嘗試優化整個if-else if塊?
你應該明確這樣做:
if (A)
return true;
else if (B)
return true;
...
else if (Y)
return true;
else {
if (likely(Z))
return true;
raiseError();
return false;
}
現在編譯器清楚地了解您的意圖,並且不會重新分配其他分支概率。 代碼的可讀性也增加了。
PS我建議你重寫Linux內核以防止靜默整體轉換的方式也可能和不太可能:
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
一般來說,GCC假設if語句中的條件是真的 - 有例外,但它們是上下文的。
extern int s(int);
int f(int i) {
if (i == 0)
return 1;
return s(i);
}
產生
f(int):
testl %edi, %edi
jne .L4
movl $1, %eax
ret
.L4:
jmp s(int)
而
extern int t(int*);
int g(int* ip) {
if (!ip)
return 0;
return t(ip);
}
生產:
g(int*):
testq %rdi, %rdi
je .L6
jmp t(int*)
.L6:
xorl %eax, %eax
ret
(見godbolt )
注意f
的分支是jne
(假設條件為真),而在g
中假定條件為假。
現在與以下內容進行比較:
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));
}
哪個產生
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)
在這里我們看到一個上下文因素:GCC發現它可以在這里重用L2
,所以它決定不太可能認為最終的條件,以便它可以發出更少的代碼。
讓我們看一下你給出的例子的匯編:
#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;
}
程序集看起來像這樣 :
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
請注意,生成的代碼在!Z為真時分支,它的行為就像Z可能一樣。 如果我們告訴Z可能會發生什么?
#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;
}
現在我們得到了
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
這里的要點是,在使用這些宏時要謹慎,並仔細檢查前后的代碼,以確保獲得預期的結果,並進行基准測試(例如使用perf)以確保處理器正在進行對齊的預測使用您生成的代碼。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.