匯編高效乘法運算的具體使用方法
乘法指令是一種在CPU中實現(xiàn)的基本算術(shù)操作,用于計算兩個數(shù)的乘積。在匯編語言中,乘法指令通常是通過mul(無符號乘法)
和imul(有符號乘法)
這兩個指令實現(xiàn)的。由于乘法指令在執(zhí)行時所消耗的時鐘周期較多,所以編譯器在優(yōu)化代碼時通常會嘗試將乘法操作轉(zhuǎn)換為更高效的加法、和移位操作。
對于較小的數(shù),編譯器可能會選擇將乘法操作直接轉(zhuǎn)換為加法操作。例如,將表達式
a * b
轉(zhuǎn)換為a + a + ... + a
(b次相加)的形式。這種方式可以通過循環(huán)展開、代碼向量化等技術(shù)來優(yōu)化。對于較大的數(shù),編譯器可能會使用位移和移位操作來代替乘法。例如,將表達式
a * b
轉(zhuǎn)換為a << n + a << m
的形式,其中n
和m
為符合條件的位數(shù)。這種方式可以通過位移指令的高效性來加速運算。
當以上方式均無法進行優(yōu)化時,編譯器才會使用mul/imul
指令來執(zhí)行乘法操作。這兩條指令可以對無符號數(shù)和有符號數(shù)進行乘法運算,即便這兩條指令會使用更多的時鐘周期,但乘法指令的計算效率相對于其他指令DIV
來說仍然較低,因此在編寫高效代碼時,應盡可能地避免使用乘法操作,并結(jié)合使用上面提到的技巧進行優(yōu)化。
使用IMUL指令完成乘法
要計算乘法在不考慮執(zhí)行效率的情況下編譯器通常會直接使用imul
指令完成計算,imul指令在一些情況下可以比其他乘法指令(如mul指令)更快地執(zhí)行乘法運算,但性能較低的原因主要是由于imul指令通常用于有符號數(shù)的乘法運算,并且在執(zhí)行時需要處理符號位的擴展和溢出問題,這轉(zhuǎn)換成了額外的指令和時鐘周期的消耗。如果對于無符號整數(shù)或需要使用寄存器的低位或者高位結(jié)果的情況,使用imul指令可以提供一定的優(yōu)勢。
計算乘法時應遵循:
- 如果乘數(shù)與被乘數(shù)都是
8位
則把AL
做乘數(shù),結(jié)果放在AX
中 - 如果乘數(shù)與被乘數(shù)都是
16位
將把AX
做乘數(shù),結(jié)果放在EAX
中 - 如果乘數(shù)與被乘數(shù)都是
32位
將把EAX
做乘數(shù),結(jié)果放在EDX:EAX
中
乘法指令計算很簡單,只需要累加乘數(shù)即可,如下所示則是一個簡單的計算三個數(shù)相乘的匯編實現(xiàn);
.data x DWORD ? y DWORD ? z DWORD ? szFmt BYTE '計算結(jié)果: %d',0dh,0ah,0 .code main PROC mov dword ptr ds:[x],10 mov dword ptr ds:[y],24 mov dword ptr ds:[z],18 ; 計算 x * y * z mov eax,dword ptr ds:[x] imul eax,dword ptr ds:[y] imul eax,dword ptr ds:[z] invoke crt_printf,addr szFmt,eax main ENDP END main
使用LEA指令替換乘法
在實際編程中,我們可以使用LEA指令來替代乘法操作,從而提高代碼的執(zhí)行效率。但讀者需要注意,在使用LEA計算乘法時必須要保證乘數(shù)是2
的次冪,并且乘數(shù)的范圍必須是2/4/8
這三個區(qū)間才可使用該指令,我們使用匯編來實現(xiàn)計算eax*8+2
其匯編指令如下。
- 假設
eax=5
計算eax * 8 + 2
的結(jié)果,拆分過程如下: - 1.計算
lea ebx,dword ptr ds:[eax * 8 + 2]
這就相當于計算ebx = (eax * 8) +2
直接可得到結(jié)果。
第一個案例比較簡單,可直接使用一條lea指令即可完成計算過程,只要保證被乘數(shù)是2的次冪即可。
.data x DWORD ? szFmt BYTE '計算結(jié)果: %d',0dh,0ah,0 .code main PROC ; 針對乘法的lea指令優(yōu)化 mov dword ptr ds:[x],5 mov eax,dword ptr ds:[x] ; eax = x xor ebx,ebx ; ebx = 0 lea ebx,dword ptr ds:[eax * 8 + 2] ; ebx = eax * 8 + 2 invoke crt_printf,addr szFmt,ebx invoke ExitProcess,0 main ENDP END main
使用LEA指令拆分計算
如果我們計算的乘法超出了2/4/8
次冪范圍,則需要對乘法進行拆分,拆分時也應遵循2的次冪原則,拆分后在分開來計算。
- 假設
eax=3
計算15 * eax
的結(jié)果,拆分過程如下: - 1.計算
lea edx,[eax * 4 + eax]
這就相當于計算edx = (4 * eax) + eax = 5eax
其中的每個edx
就相當于5個eax
- 2.計算
lea edx,[edx * 2 + edx]
這就相當于計算edx = (5 * eax) * 2 + (5 * eax)
- 3.計算
(5eax * 2) = 10eax
接著計算(5 * eax) = 5eax
最后得出10eax + 5eax
- 4.經(jīng)過該過程可得出
eax * 15 = 45
最終計算3*15=45
得到最終結(jié)果.
這個計算過程看似復雜,但如果將其轉(zhuǎn)化為匯編指令那么只需要兩條即可實現(xiàn)快速乘法運算。
.data x DWORD ? szFmt BYTE '計算結(jié)果: %d',0dh,0ah,0 .code main PROC ; 針對乘法的lea指令優(yōu)化 mov dword ptr ds:[x],3 ; 如果使用lea計算乘法,則乘數(shù)必須是2/4/8 mov eax,dword ptr ds:[x] ; eax = 3 lea edx,dword ptr ds:[eax * 4 + eax] ; edx = 4eax + eax 得出 5eax,也就是說每一個edx就代表5個eax lea edx,dword ptr ds:[edx * 2 + edx] ; edx = (5eax * 2) + 5eax 最終得出 15eax invoke crt_printf,addr szFmt,edx ; edx = eax * 15 計算后得出 45 invoke ExitProcess,0 main ENDP END main
使用LEA指令遞減計算
如果計算乘法時乘數(shù)非2的次冪,這種情況下需要減去特定的值,例如當我們計算eax * 7
時,由于7非二的次冪,我們無法通過lea
指令進行計算,但我們可以計算eax * 8
計算出的結(jié)果減去一個eax
同樣可以得到正確的值。
- 假設
eax=3
計算eax * 7 + 10
的結(jié)果,拆分過程如下: - 1.計算
lea edx,dword ptr ds:[eax * 8]
這就相當于計算edx = (8 * eax)
- 2.計算
sub edx,eax
這就相當于計算edx = (8 * eax) - eax
- 3.計算
add edx,10
這就相當于計算edx = ( (8 * eax) - eax ) + 10
- 4.經(jīng)過如上計算,我們就可以計算出
eax * 7 + 10
的最終結(jié)果
這個計算過程看似復雜,但其實在匯編層面并不難構(gòu)建,如下分別實現(xiàn)計算兩個表達式求值過程。
.data x DWORD ? szFmt BYTE '計算結(jié)果: %d',0dh,0ah,0 .code main PROC ; 針對乘法的lea指令優(yōu)化 mov dword ptr ds:[x],3 ; 如果計算乘法時乘數(shù)非2的次冪,則此時需要減 ; 計算 edx = eax * 7 + 10 mov eax,dword ptr ds:[x] ; eax = 3 => 計算 eax * 7 + 10 lea edx,dword ptr ds:[eax * 8] ; edx = eax * 8 sub edx,eax ; edx = edx - eax add edx,10 ; edx = edx + 10 invoke crt_printf,addr szFmt,edx ; edx = eax * 7 + 10 ; 計算 edx = eax * 3 - 7 mov eax,dword ptr ds:[x] ; eax = 3 => 計算 eax * 3 - 7 lea edx,dword ptr ds:[eax * 2] ; edx = eax * 2 add edx,eax ; edx = edx + eax sub edx,7 ; edx = edx - 7 invoke crt_printf,addr szFmt,edx ; edx = eax * 3 - 7 invoke ExitProcess,0 main ENDP END main
使用SHL計算無符號乘法
通過使用邏輯左移同樣可以實現(xiàn)2的次冪的高速乘法運算,但邏輯左移只能用于計算無符號乘法,且只能計算被乘數(shù)是2的次方的算式。
計算時我們需要參考次方表,這里我列舉出幾個常用的次方數(shù)值:
次方表: 1=>2 2=>4 3=>8 4=>16 5=>32 6=>64 7=>128
次方表: 8=>256 9=>512 10=>1024 11=>2048 12=>4096 13=>8192 14=>16384
假設
eax=3
計算eax * 8 + 10
的結(jié)果,拆分過程如下:1.計算
shl eax,3
這就相當于計算eax = eax * 2 ^(次方) 3
其公式相當于計算eax = eax * 8
2.計算
add eax,10
這就相當于計算eax = (eax * 8) + 10
3.最終即可得到計算結(jié)果也就是
3*8+10
得到34
通過使用邏輯左移,我們可以實現(xiàn)快速無符號乘法運算,如下代碼是效率最高的一種。
.data x DWORD ? szFmt BYTE '計算結(jié)果: %d',0dh,0ah,0 .code main PROC mov dword ptr ds:[x],3 ; 計算 eax = eax * 2 ^ 1 相當于計算 eax * 2 mov eax,dword ptr ds:[x] shl eax,1 invoke crt_printf,addr szFmt,eax ; 計算 eax = eax * 2 ^ 2 相當于計算 eax * 4 mov eax,dword ptr ds:[x] shl eax,2 invoke crt_printf,addr szFmt,eax ; 計算 eax = eax * 2 ^ 3 相當于計算 eax * 8 mov eax,dword ptr ds:[x] shl eax,3 add eax,10 invoke crt_printf,addr szFmt,eax invoke ExitProcess,0 main ENDP END main
使用SAL計算有符號乘法
通過使用算數(shù)左移同樣可以實現(xiàn)2的次冪的高速乘法運算,與邏輯左移不同,算術(shù)左移只能計算有符號乘法,且只能計算被乘數(shù)是2的次方的算式。
計算時我們需要參考次方表,這里我列舉出幾個常用的次方數(shù)值:
次方表: 1=>2 2=>4 3=>8 4=>16 5=>32 6=>64 7=>128
次方表: 8=>256 9=>512 10=>1024 11=>2048 12=>4096 13=>8192 14=>16384
假設
eax=-5,ebx=3
計算(eax * 8) + (ebx * 4)
的結(jié)果,拆分過程如下:1.計算
sal eax,3
這就相當于計算eax = (eax * 2 ^ 3 )
其公式相當于計算eax = eax * 8
結(jié)果是一個有符號數(shù)2.計算
shl ebx,2
這就相當于計算ebx = (ebx * 2 ^2)
其公式相當于計算ebx = ebx * 4
結(jié)果是一個無符號數(shù)3.最終將有符號與無符號數(shù)通過
add eax,ebx
相加,即可得到(eax * 8) + (ebx * 4)
的最終結(jié)果-28
如下是通過算數(shù)左移,實現(xiàn)2的次冪的高速乘法運算,我們可以將算數(shù)運算與邏輯運算相加通過此方式提高運算效率。
.data x DWORD ? y DWORD ? szFmt BYTE '計算結(jié)果: %d',0dh,0ah,0 .code main PROC mov dword ptr ds:[x],-5 mov dword ptr ds:[y],3 ; 計算 eax = eax * 2 ^ 1 相當于計算 eax * 2 mov eax,dword ptr ds:[x] sal eax,1 invoke crt_printf,addr szFmt,eax ; 計算 eax = eax * 2 ^ 2 相當于計算 eax * 4 mov eax,dword ptr ds:[x] sal eax,2 invoke crt_printf,addr szFmt,eax ; 計算 eax = (eax * 2 ^ 3 ) + (ebx * 2 ^2) 相當于計算 (eax * 8) + (ebx * 4) mov eax,dword ptr ds:[x] mov ebx,dword ptr ds:[y] sal eax,3 ; eax * 8 (有符號乘法) shl ebx,2 ; ebx * 4 (無符號乘法) add eax,ebx ; eax + ebx invoke crt_printf,addr szFmt,eax invoke ExitProcess,0 main ENDP END main
乘法優(yōu)化的知識點基本就這些,除了兩個未知變量的相乘無法優(yōu)化外,其他形式的乘法運算均可以進行優(yōu)化,如果表達式中存在一個常量值,那編譯器則會匹配各種優(yōu)化策略,最后對不符合優(yōu)化策略的運算進行調(diào)整,如果真的無法優(yōu)化,則會使用原始乘法指令計算。
到此這篇關于匯編高效乘法運算的具體使用方法的文章就介紹到這了,更多相關匯編 乘法運算內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用匯編語言實現(xiàn)if else 循環(huán)函數(shù)調(diào)用的具體方法
這篇文章主要介紹了使用匯編語言實現(xiàn)if else 循環(huán)函數(shù)調(diào)用的具體方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-01-01