閱讀655 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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上有一些類似的問題 12。 我理解這應該是gcc編譯器的高版本對類型轉換規則的識別可能做的更加好,細節不太了解,如有看到這篇文章的朋友,求幫忙修正 :)

!!!無論如何, 如果你需要打開strict aliasing, 一定要打開Wstrict-aliasing,消除代碼warning。 同時在代碼上也要盡量減少這種不同類型的轉換。

在MySQL移除-fno-strict-aliasing後, 也看到了一些擔憂,因為mysql的codebase畢竟已經相當古老了, 而當前並沒有一些靜態或動態的分析工具能夠找到所有違反strict aliasing規則的地方。可能存在潛在的風險。

最後更新:2017-08-13 22:36:50

  上一篇:go  我遇到的十二種Mysql連接錯誤實例
  下一篇:go  Gartner公布2017年全球雲存儲魔力象限:阿裏雲躋身四強