[英]What optimizations does __builtin_unreachable facilitate?
從 gcc 的文檔來看
如果控制流到達
__builtin_unreachable
的點,則程序未定義。
我認為__builtin_unreachable
可以以各種創造性的方式用作優化器的提示。 所以我做了一個小實驗
void stdswap(int& x, int& y)
{
std::swap(x, y);
}
void brswap(int& x, int& y)
{
if(&x == &y)
__builtin_unreachable();
x ^= y;
y ^= x;
x ^= y;
}
void rswap(int& __restrict x, int& __restrict y)
{
x ^= y;
y ^= x;
x ^= y;
}
編譯為(g++ -O2)
stdswap(int&, int&):
mov eax, DWORD PTR [rdi]
mov edx, DWORD PTR [rsi]
mov DWORD PTR [rdi], edx
mov DWORD PTR [rsi], eax
ret
brswap(int&, int&):
mov eax, DWORD PTR [rdi]
xor eax, DWORD PTR [rsi]
mov DWORD PTR [rdi], eax
xor eax, DWORD PTR [rsi]
mov DWORD PTR [rsi], eax
xor DWORD PTR [rdi], eax
ret
rswap(int&, int&):
mov eax, DWORD PTR [rsi]
mov edx, DWORD PTR [rdi]
mov DWORD PTR [rdi], eax
mov DWORD PTR [rsi], edx
ret
我假設從優化器的角度來看, stdswap
和rswap
是最佳的。 為什么brswap
不被編譯成同樣的東西? 我可以用__builtin_unreachable
讓它編譯成同樣的東西嗎?
__builtin_unreachable
的目的是幫助編譯器刪除死代碼(程序員知道永遠不會被執行)並通過讓編譯器知道路徑是“冷”來線性化代碼。 考慮以下:
void exit_if_true(bool x);
int foo1(bool x)
{
if (x) {
exit_if_true(true);
//__builtin_unreachable(); // we do not enable it here
} else {
std::puts("reachable");
}
return 0;
}
int foo2(bool x)
{
if (x) {
exit_if_true(true);
__builtin_unreachable(); // now compiler knows exit_if_true
// will not return as we are passing true to it
} else {
std::puts("reachable");
}
return 0;
}
生成的代碼:
foo1(bool):
sub rsp, 8
test dil, dil
je .L2 ; that jump is going to change
mov edi, 1
call exit_if_true(bool)
xor eax, eax ; that tail is going to be removed
add rsp, 8
ret
.L2:
mov edi, OFFSET FLAT:.LC0
call puts
xor eax, eax
add rsp, 8
ret
foo2(bool):
sub rsp, 8
test dil, dil
jne .L9 ; changed jump
mov edi, OFFSET FLAT:.LC0
call puts
xor eax, eax
add rsp, 8
ret
.L9:
mov edi, 1
call exit_if_true(bool)
注意差異:
xor eax, eax
和ret
被刪除,因為現在編譯器知道這是一個死代碼。 puts
調用,因此條件跳轉可以更快(未預測的前向分支在沒有預測信息時更快)。 這里的假設是以noreturn
函數調用或__builtin_unreachable
結尾的分支將只執行一次或導致longjmp
調用或異常拋出,這兩種情況都很少見,並且在優化期間不需要優先處理。
您正在嘗試將其用於不同的目的 - 通過提供有關別名的編譯器信息(您可以嘗試對齊進行相同操作)。 不幸的是,GCC不理解這種地址檢查。
正如您所注意到的那樣,添加__restrict__
會有所幫助。 所以__restrict__
適用於別名, __builtin_unreachable
不適用。
請看以下使用__builtin_assume_aligned
示例:
void copy1(int *__restrict__ dst, const int *__restrict__ src)
{
if (reinterpret_cast<uintptr_t>(dst) % 16 == 0) __builtin_unreachable();
if (reinterpret_cast<uintptr_t>(src) % 16 == 0) __builtin_unreachable();
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst[3] = src[3];
}
void copy2(int *__restrict__ dst, const int *__restrict__ src)
{
dst = static_cast<int *>(__builtin_assume_aligned(dst, 16));
src = static_cast<const int *>(__builtin_assume_aligned(src, 16));
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst[3] = src[3];
}
生成的代碼:
copy1(int*, int const*):
movdqu xmm0, XMMWORD PTR [rsi]
movups XMMWORD PTR [rdi], xmm0
ret
copy2(int*, int const*):
movdqa xmm0, XMMWORD PTR [rsi]
movaps XMMWORD PTR [rdi], xmm0
ret
您可以假設編譯器可以理解dst % 16 == 0
表示指針是16字節對齊的,但事實並非如此。 因此使用未對齊的存儲和加載,而第二個版本生成更快的指令,需要對齊地址。
我認為你是試圖微量優化你的代碼錯誤的方向錯誤。
__builtin_unreachable以及__builtin_expect執行預期的操作 - 在您的情況下jnz
使用的if運算符中刪除不必要的cmp
和jnz
。
編譯器應該使用您編寫的C代碼生成機器代碼,以生成可預測的程序。 在優化過程中,它能夠找到並優化(即用更好的機器代碼版本替換)一些模式,當優化算法已知時 - 這樣的優化不會破壞程序行為。
比如像
char a[100];
for(int i=0; i < 100; i++)
a[i] = 0;
將替換單個調用庫std :: memset(a,0,100),它是使用匯編實現的,並且是當前CPU架構的最佳選擇。
以及編譯器能夠檢測
x ^= y;
y ^= x;
x ^= y;
並用最簡單的mashie代碼替換它。
我認為你的if運算符和未達到的指令會影響編譯器優化器,因此無法進行優化。
在交換兩個整數的情況下,第三個臨時交換變量可以通過編譯器自己刪除,即它會像
movl $2, %ebx
movl $1, %eax
xchg %eax,%ebx
其中ebx和eax寄存器值實際上是你的x和y。 你可以像自己一樣實現它
void swap_x86(int& x, int& y)
{
__asm__ __volatile__( "xchg %%rax, %%rbx": "=a"(x), "=b"(y) : "a"(x), "b"(y) : );
}
...
int a = 1;
int b = 2;
swap_x86(a,b);
什么時候使用__builtin_unreachable? 可能當你知道某些情況幾乎不可能時,但邏輯上可能會發生。 即你有一些功能
void foo(int v) {
switch( v ) {
case 0:
break;
case 1:
break;
case 2:
break;
case 3:
break;
default:
__builtin_unreachable();
}
}
並且您知道v
參數值始終在0和3之間。但是,int范圍是-2147483648
到2147483647
(當int是32位類型時),編譯器不知道實際值范圍並且無法刪除默認塊(如以及一些cmp指令等),但如果你不將這個塊添加到交換機中,它會警告你。 所以在這種情況下__builtin_unreachable
可能有所幫助。
可能當您知道某些情況實際上是不可能的,但從邏輯上講它可能會發生。
使用 __builtin_unreachable() 時要非常小心; 默認情況下:案例標簽。 我花了幾天時間試圖找出 .rodata 區域的隨機分支。 在我的例子中,有 0 到 25 個案例標簽和一個默認值:案例,用於從 0 到 128 的枚舉。以及導致問題的開關值 71。 6.3.0 有范圍檢查代碼,但 8.3.0 對其進行了優化。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.