李忠武
(保山學院,云南保山,678000)
基于匯編語言教學中優化語言代碼方法探討與研究
李忠武
(保山學院,云南保山,678000)
本文討論一些優化X86匯編語言代碼的簡單編程技巧。建議把這些技巧應用在運行于Intel最新微架構(包括Haswell、Sandy Bridge和Nehalem)代碼中。大多數技巧同樣適用于更早的微架構。可以把優化技巧和輔助性的指導方針分為五大類:基本優化、浮點算術、程序分支、數據對齊、SIMD技巧。
基本優化;浮點算術;程序分支;數據對齊;SIMD技巧
需要謹記的一個要點是,接下來的基本優化、浮點算術、程序分支、數據對齊、SIMD技巧優化技巧都必須謹慎使用。[1]例如,如果僅為避免使用不推薦的指令形式一次,就增加多條額外的push和pop指令,那么是沒有意義的。
下面列出了一些常用于提高X86匯編語言代碼性能的基本優化技巧。
(1)盡可能使用test指令,而不是cmp指令。
(2)盡可能避免使用內存與立即數形式的cmp和test指令(例如,cmp dword ptr[ebp+16],100或者test byte ptr[r12],0fh)。最好先將內存值載入寄存器,然后使用寄存器與立即數形式的cmp和test指令(如mov eax,dword ptr[ebp+16],接著cmp eax,100)。
(3)使用add或者sub指令,而不是inc或者dec指令,特別是在性能關鍵的循環中。后面的兩個指令不會更新EFLAGS寄存器中的所有標志位,通常會慢一些。
(4)使用or、sub、pxor、xorps等指令將一個寄存器置0,而不是用數據傳送指令。例如,xor eax,eax和xorps xmm0, xmm0比mov eax,0和movaps xmm0,xmmword ptr[XmmZero]要好。
(5)在所有操作數寬度前綴的指令中,避免使用16位立即數,而應該使用對應的32位或者8位立即數。例如,使用mov edx,42而不是mov dx,42。
(6)展開(或者部分展開)循環次數是常數的小循環。(7)將在計算中多次使用的內存值載入寄存器。如果一個內存值只在一次計算中用到,用寄存器到內存形式的計算指令。如表1顯示了幾個例子。

表1 一次使用和多次使用內存值的指令形式
下面優化技巧適用于x86-64代碼。
(1)當操作數是32時,使用32位通用寄存器和指令形式。
(2)操作32位寬數值時,優先使用通用寄存器EAX、ECX、EDX、ESI、EDI,而不是寄存器R8D-R15D。對于后面的寄存器組,指令解碼要多一個字節。
(3)利用額外的通用寄存器和SIMD寄存器,以減少數據依賴和寄存器溢出。
(4)如果不需要完成的128位結果,用兩操作數或三制作數形式的imul指令進行兩個64位整數乘法。
使用匯編語言進行浮點算術運算時,應考慮下面的指導方針:首先,在新代碼中使用x86-SSE或者x86-AVX而不是x86 FPU的標題浮點指令;其次,在算術運算中,盡可能避免算術下溢和非正規值;三是避免使用非正規浮點常量;最后是如果預知會有多次算術下溢,考慮啟用清洗到零(MXCSR.FZ)和非正規為零(MXCSR.DAX)模式。
程序分支指令如jmp、call和ret在執行時是潛在的耗時操作,因為它們可能影響前端流水線和內部緩存的內容。考慮到使用的頻率,條件跳轉指令jcc也可能帶來性能問題。下面的優化技巧能最小化分支指令對性能的影響,并且提高分支預測單元的準確性。
(1)組織代碼,盡量少使用分支指令。
(2)使用setcc和cmovcc指令,以消除不可預測的數據相關的分支。
(3)在性能關鍵的循環中,對齊分支目標的邊界到16字節。
(4)將不太可能執行的條件代碼(例如錯誤處理代碼)移到另外的程序段或內存頁。
當預測一個分支語句的目標時,分支預測單元采用靜態和動態技術。當包含條件跳轉指令的代碼能夠組織成與分支預測單元的靜態預測算法一致時,那么錯誤的分支預測就可以被最小化。
(1)當貫代碼可能被執行時,使用向前的條件跳轉。
(2)當貫穿代碼不可能被執行時,使用身后條件跳轉。
向前條件跳轉方法經常用在檢查函數參數的代碼塊中。向后條件跳轉技術可以用在程序循環代碼塊的底部(緊跟著一個計數器更新或者其他循環結束判斷)。清單1包含了一小段匯編語言函數,展示了這些實踐經驗的細節。
清單1 使用符合靜態分支預測算法的條件跳轉指令
model flat,c
code
;extern ”c” bool CalcReslut_(double* des,const double* src,int n);
CalcReslut_proc
push ebp
mov ebp,esp
push esi
push edi
;本代碼使用向前條件跳轉,因為貫穿的情況可能發生
mov edi,[ebp+8]
test edi,0fh
jnz Error
mov esi,[ebp+12]
test esi,0fh
jnz Error
test ecx,2
jl Error
test ecx,1
jnz Error
;簡單的數據處理循環
xor eax,eax
@@:movapd xmm0,xmmword ptr[esi+eax]
mulpd xmm0,xmm0
movapd xmmword ptr[edi+eax],xmm0
;本段代碼使用向后條件跳轉,因為貫穿的情況更不能發生
add eax,16
sub ecx,2
jnz @B
mov eax,1
pop edi
pop esi
pop dbp
ret
;錯誤處理代碼,不太可能執行
Error:xor eax,eax
pop edi
pop esi
pop ebp
ret
CalcReslut_ endp
end
操作對齊錯誤數據時,可能導致處理器介紹費額外的內存周期和執行更多微指令,這將會給整個系統的性能帶來負面影響。下面的數據對齊實踐應該被認為是普遍真理并一直遵守。
(1)將多字節整數和浮點數對齊到自然的邊界。
(2)將64、128和256位寬的組合數據對齊到它們本身的邊界。
(3)必要時填補數據結構,以保證正確對齊。
(4)使用恰當的編譯器指令和庫函數,以對齊高層代碼分配的數據項。例如_declspec(align(n))指示器和_aligned_ malloc函數能用來正確地對齊Visual C++函數中分配的數據項。[2]
(5)更多地使用存儲對齊,而不是加載對齊。
下面這些對齊技巧也推薦使用:將小數組和短字符串對齊安置在數據結構中,以避免緩存行分割。評估不同的數據布局對性能的影響,例如數組結構與結構數組。
在任何函數中,用x86-SSE和x86-AVX計算資源時應該考察下面這些技巧是否適用。
(1)消除寄存器依賴,以利用執行引擎的多個執行單元。
(2)用組合的單精度浮點數代替雙精度浮點數。
(3)將多次使用的內存操作數和組織常量加載到寄存器。
(4)用對齊的傳送指令存儲和加載組織數據,例如movdqa、movaps、movapd等。
(5)用小的數據塊處理SIMD數組,以最大化重用駐留緩存數據。
(6)在x86-AVX代碼中,使用數據混合而不是數據重組。
(7)當需要避免x86-AVX到x86-SSE狀態遷移的損失時,使用vzeroupper指令。
(8)使用x86-AVX vgather指令的雙字形式,而不是四字形式。在數據要用到之前就完成需要的收集操作。
(9)下面的這些實踐可以用于提高特定算法性能(進行SIMD編碼和解碼操作):
(10)使用無時態存儲指令(如movntdqa、movntpd、movntps等),以最小化緩存污染。
(11)使用數據項預取指令(例如perfecht0,perfetchnta等),以通知處理器預期要使用的數據項。
使用匯編語言優化代碼是一件困難,而且技巧性很強的工作。很多編譯器能夠生成為處理器進行過特殊優化處理的代碼,一旦進行修改,這些特殊優化可能就會被破壞而失效。因此,在你決定使用自己的匯編代碼之前,一定要測試一下,到底是編譯器生成的那段代碼更好,還是你的更好。
[1] Kusswurm,D.現代X86匯編語言程序設計[M].北京:機械工業出版社,2016:446.
[2]黃永才.Visual C++程序設計[M].北京:清華大學出版社,2017:251.
[3]高偉.SIMD自動向量化編譯優化概述[J]. 軟件學報,2015,(6):1269-1276.
Discussion and Research on the method of optimizing language code in assembly language teaching
Li Zhongwu
(Baoshan college,Baoshan Yunan, 678000)
This article discusses some simple programming techniques for optimizing X86 assembly language code It is recommended that these techniques be applied to code running in the latest Intel micro architectures (including Haswell, Sandy, Bridge, and Nehalem) Most techniques apply equally well to earlier architectures. Tuning and assistive guidelines can be divided into five broad categories: basic optimization, floating point arithmetic, program branching, data alignment, and SIMD techniques
basic optimizations; floating-point arithmetic; program branches; data alignment; SIMD techniques