MySQL編譯選項-fno-strict-aliasing隨手記

zhaiwx_yinfeng發表於2017-08-03

最近釋出的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
", 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規則的地方。可能存在潛在的風險。


相關文章