這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
為啥寫
Go的彙編一直是我感興趣的地方,為了驗證之前所學的彙編知識和好玩 ,我決定往Go官方提交一個效能patch。所以到官方的標準庫裡搜了一圈,發現adler32並沒有硬體加速的實現,而Intel已經公布了相關的SSE加速實現https://github.com/01org/isa-l/
所以我決定把Intel的抄過來,結果不停地掉坑和爬出來,終於提交了patch(撒花)
https://go-review.googlesource.com/c/51850
希望在Go 1.10發布的時候能進入官方源:)
坑
- 寫法的區別
- Go彙編不支援
- 難以調試
- 記憶體越界與LEA
- 向官方提交代碼需要注意的地方,保證Change ID一致
坑一:Intel和AT&T的寫法的區別
Intel寫法是:opcode source destination
AT&T 也就是Go官方組合語言的寫法正好是反過來的:opcode destination source
抄代碼的時候有點繞
坑二 Go彙編不支援:
Go的維護者們對於新添加彙編opcode一直是很保守的,比如2001年前後就有的SSE2 裡的PSHLLW()竟然不支援(= =||)。所以得自己填BYTE,比如官方文檔中的MOVQ用BYTE方式編寫 https://golang.org/doc/asm
BYTE $0x0f; BYTE $0x6f; BYTE $0x00 // MOVQ (AX), M0
但這裡就有一個坑,比如PSHLLD和PSHRQ的Opcode是一樣的……只是按/r 寄存器類型進行區別。需要注意
坑三:調試困難
一般代碼測試時,都可以直接輸出日誌,協助定位問題,但是彙編不行,所以我是通過把需要的值放入某個不用的寄存器, 例如
#define debug R15// min(a, b int) intTEXT min(SB), NOSPLIT, $0
在需要的時候,提前把函數返回
MOVQ debug, ret+16(FP)RET
當然應該有更好的方法,可以把所有寄存器列印出來,不過這個方法夠我自己調試用了。
坑四:記憶體越界與LEA
一般程式中都是指標,或者直接結構體。不過,彙編這裡迴歸本真,只有記憶體位址和寄存器。所以一定要小心訪問資料的邊界和跳轉的記憶體位址。這裡學到了一個LEA的用法,地址計算機,把記憶體位址到目標寄存器裡。
LEAQ 0(data)(size*1), end
具體用法
- 0: 寫死的位移量
- data: 位移值
- size: 動態位移量,可以用乘法
坑五:
提交代碼到gerrit要保持Change ID相同,要不然算一個新的Change。
附錄:opcode坑
- MOVOU: O=oct, U=unaligned, 指的是八個word,即128位,用於XXM寄存器的移動。
- TESTQ: 對比值,並影響FLAG
- DIVL:低位除,如果高位有資料就會出錯
資料:Go官方介紹: https://golang.org/doc/asm