MySQL編譯選項 -fno-strict-aliasing隨手記
最近發布的MySQL8.0.2版本中,將gcc的編譯選項從--fno-strict-aliasing移除,也就是說打開strict aliasing, 根據worklog #10344 的描述,在單線程的性能測試中,有最多%4的性能提升,還是相當可觀的。這個flag在我們內部編譯版本中也是一直打開的,但一直不知甚解。本文是網上搜索文檔和自己試驗的小結。
首先strict aliasing是個什麼鬼? --fno-strict-aliasing對應的是--f-strict-aliasing,GCC文檔的解釋如下:
Allow the compiler to assume the strictest aliasing rules applicable to the language being compiled. For C (and C++), this activates optimizations based on the type of expressions. In particular, an object of one type is assumed never to reside at the same address as an object of a different type, unless the types are almost the same. For example, an unsigned int can alias an int, but not a void* or a double. A character type may alias any other type.
Stackoverflow上關於strict aliasing規則的問題
當使用strict aliasing時, 編譯器會認為在不同類型之間的轉換不會發生,因此執行更激進的編譯優化,例如reorder執行順序。
Strcit aliasing隻能隱式的開啟或者顯式的禁止掉。在-o2或更高的編譯級別上會被隱式開啟。
這裏舉個簡單的例子,參考網絡 上這篇文章
$cat x.c
#include <stdio.h>
#include <stdint.h>
int main()
{
int a = 0x12345678;
uint16_t* const sp = (uint16_t*)&a;
uint16_t hi = sp[0];
uint16_t lo = sp[1];
sp[1] = hi;
sp[0] = lo;
printf("%x\n", a);
return 0;
}
函數的功能很簡單,對一個數字的高低位進行交換
gcc版本
$gcc --version
gcc (GCC) 4.4.6 20110731 (Red Hat 4.4.6-3)
執行strict aliasing (O2及以上默認打開)
$gcc -O2 x.c
$./a.out
12345678
非strict aliasing (顯式指定-fno-strict-aliasing)
$gcc -O2 -fno-strict-aliasing x.c
$./a.out
56781234
不同的gcc flag,兩次的執行結果居然完全不相同,隻有第二次才實現了預期功能。因為默認情況下不報warning,我們把告警打開看看:
$gcc -O2 -Wstrict-aliasing x.c
x.c: In function ‘main’:
x.c:13: warning: dereferencing pointer ‘sp’ does break strict-aliasing rules
x.c:9: warning: dereferencing pointer ‘sp’ does break strict-aliasing rules
x.c:8: note: initialized from here
x.c:10: warning: dereferencing pointer ‘({anonymous})’ does break strict-aliasing rules
x.c:10: note: initialized from here
x.c:12: warning: dereferencing pointer ‘({anonymous})’ does break strict-aliasing rules
x.c:12: note: initialized from her
果然在使用strict aliasing時,因為破壞了嚴格aliasing的規則大量報警,因此如果我們要使用strict aliasing,一定要打開報警,並重視每個warning。
回到剛才的問題,為什麼strict aliasing會輸出最原始的數據,而不是修改後的數據呢 ? 看起來就好像後麵的修改全部被忽略掉了一樣。 我們來看看編譯後的代碼。可以看到兩個匯編代碼完全不相同。編譯器認為代碼裏不可能出現不規範的類型轉換,所以在錯誤的案例裏,a的未被修改的值被直接拋給了printf函數
正確的 (gcc -O2 -fno-strict-aliasing x.c)
(gdb) disassemble main
Dump of assembler code for function main:
0x00000000004004d0 <+0>: sub $0x18,%rsp
0x00000000004004d4 <+4>: mov $0x4005f8,%edi
0x00000000004004d9 <+9>: xor %eax,%eax
0x00000000004004db <+11>: movw $0x5678,0xe(%rsp)
0x00000000004004e2 <+18>: movw $0x1234,0xc(%rsp)
0x00000000004004e9 <+25>: mov 0xc(%rsp),%esi
0x00000000004004ed <+29>: callq 0x4003b8 <printf@plt>
0x00000000004004f2 <+34>: xor %eax,%eax
0x00000000004004f4 <+36>: add $0x18,%rsp
0x00000000004004f8 <+40>: retq
End of assembler dump.
錯誤的 (gcc -O2 -fstrict-aliasing x.c)
(gdb) disassemble main
Dump of assembler code for function main:
0x00000000004004d0 <+0>: sub $0x18,%rsp
0x00000000004004d4 <+4>: mov $0x12345678,%esi
0x00000000004004d9 <+9>: mov $0x4005f8,%edi
0x00000000004004de <+14>: xor %eax,%eax
0x00000000004004e0 <+16>: movw $0x5678,0xe(%rsp)
0x00000000004004e7 <+23>: movw $0x1234,0xc(%rsp)
0x00000000004004ee <+30>: callq 0x4003b8 <printf@plt>
0x00000000004004f3 <+35>: xor %eax,%eax
0x00000000004004f5 <+37>: add $0x18,%rsp
0x00000000004004f9 <+41>: retq
End of assembler dump.
但是如果我換成高版本的gcc,例如4.8版本,兩種編譯方式都沒有問題,甚至加上-Wstrict-aliasing連報警都沒有。隻有加上-Wstrict-aliasing=1才報warning
$/opt/rh/devtoolset-2/root/usr/bin/gcc --version
gcc (GCC) 4.8.2 20140120 (Red Hat 4.8.2-15)
$/opt/rh/devtoolset-2/root/usr/bin/gcc -O2 -fno-strict-aliasing x.c
$./a.out
56781234
/opt/rh/devtoolset-2/root/usr/bin/gcc -O2 -fstrict-aliasing x.c
$./a.out
56781234
$/opt/rh/devtoolset-2/root/usr/bin/gcc -O2 -fstrict-aliasing -Wstrict-aliasing=1 x.c
x.c: In function ‘main’:
x.c:9:2: warning: dereferencing type-punned pointer might break strict-aliasing rules [-Wstrict-aliasing]
uint16_t* const sp = (uint16_t*)&a;
網上搜了一下,Stackoverflow上有一些類似的問題 1, 2。 我理解這應該是gcc編譯器的高版本對類型轉換規則的識別可能做的更加好,細節不太了解,如有看到這篇文章的朋友,求幫忙修正 :)
!!!無論如何, 如果你需要打開strict aliasing, 一定要打開Wstrict-aliasing,消除代碼warning。 同時在代碼上也要盡量減少這種不同類型的轉換。
在MySQL移除-fno-strict-aliasing後, 也看到了一些擔憂,因為mysql的codebase畢竟已經相當古老了, 而當前並沒有一些靜態或動態的分析工具能夠找到所有違反strict aliasing規則的地方。可能存在潛在的風險。
最後更新:2017-08-13 22:36:50