![](/img/trans.png)
[英]OpenCV 2.4.7 with FFMpeg support build with VS 2010 (x86)
[英]x86 MUL Instruction from VS 2008/2010
Visual Studio或Visual C ++ Express的現代(2008/2010)版本是否會在編譯代碼中生成x86 MUL指令(無符號乘法)? 我似乎無法找到或設想它們出現在編譯代碼中的示例,即使使用無符號類型也是如此。
如果VS不使用MUL進行編譯,是否有理由說明原因?
imul
(signed)和mul
(unsigned)都有一個操作數形式,它執行edx:eax = eax * src
。 即32x32b => 64b全乘(或64x64b => 128b)。
186添加了一個imul dest(reg), src(reg/mem), immediate
形式,並且386添加了一個imul r32, r/m32
形式,兩者都只計算結果的下半部分。 (根據NASM的附錄B ,另見x86標簽維基 )
當將兩個32位值相乘時,無論您認為值是有符號還是無符號,結果的最低有效32位都是相同的。 換句話說,只有當你查看結果的“上半部分”時,有符號和無符號乘法之間的差異才變得明顯,一個操作數imul
/ mul
放入edx
,兩個或三個操作數imul
無處放置。 因此, imul
的多操作數形式可用於有符號和無符號值,並且英特爾也不需要添加新形式的mul
。 (他們本可以使多操作數mul
成為imul
的同義詞,但這會使反匯編輸出與源不匹配。)
在C中,算術運算的結果與操作數具有相同的類型(在窄整數類型的整數提升之后)。 如果將兩個int
相乘,則得到一個int
,而不是long long
:不保留“upper half”。 因此,C編譯器只需要imul
提供的東西,並且因為imul
比mul
更容易使用,所以C編譯器使用imul
來避免需要mov
指令來將數據輸入/輸出eax
。
作為第二步,由於C編譯器使用imul
的多操作數形式, imul
英特爾和AMD投入了盡可能快的努力。 它只寫一個輸出寄存器,而不是e/rdx:e/rax
,因此CPU可以比單操作數形式更容易地優化它。 這使得imul
更具吸引力。
在實現大數字運算時, mul
/ imul
的單操作數形式很有用。 在C中,在32位模式下,您應該通過將unsigned long long
值相乘來獲得一些mul
調用。 但是,根據編譯器和操作系統,這些mul
操作碼可能隱藏在某些專用功能中,因此您不一定會看到它們。 在64位模式下, long long
只有64位,而不是128位,編譯器只使用imul
。
x86上有三種不同類型的乘法指令。 第一個是MUL reg
,它通過reg執行EAX
的無符號乘法,並將(64位)結果放入EDX:EAX
。 第二個是IMUL reg
,它與帶符號的乘法相同。 第三種類型是IMUL reg1, reg2
(將reg1與reg2相乘並將32位結果存儲到reg1中)或IMUL reg1, reg2, imm
(將reg2與imm相乘並將32位結果存儲到reg1中)。
由於在C中,兩個32位值的乘法產生32位結果,編譯器通常使用第三種類型(符號無關緊要,低32位在有符號和無符號32x32乘法之間一致)。 如果您實際使用完整的64位結果,VC ++將生成MUL
/ IMUL
的“長乘”版本,例如:
unsigned long long prod(unsigned int a, unsigned int b)
{
return (unsigned long long) a * b;
}
IMUL
的2操作數(和3操作數)版本比單操作數版本更快,因為它們不會產生完整的64位結果。 寬乘數大而慢; 如果需要,可以更容易地構建一個較小的乘法器並使用Microcode合成長乘法。 此外,MUL / IMUL寫入兩個寄存器,這通常通過在內部將其分解為多個指令來解決 - 指令重新排序硬件更容易跟蹤每個寫入一個寄存器的兩個相關指令(大多數x86指令在內部看起來像而不是跟蹤一個寫兩個的指令。
據http://gmplib.org/~tege/x86-timing.pdf ,該IMUL
指令具有更低的延遲和更高的吞吐量(如果我正確讀取表)。 也許VS只是使用更快的指令(假設IMUL
和MUL
總是產生相同的輸出)。
我沒有Visual Studio方便,所以我試圖通過GCC獲得其他東西。 我也總是得到一些IMUL
變化。
這個:
unsigned int func(unsigned int a, unsigned int b)
{
return a * b;
}
匯總到此(使用-O2):
_func:
LFB2:
pushq %rbp
LCFI0:
movq %rsp, %rbp
LCFI1:
movl %esi, %eax
imull %edi, %eax
movzbl %al, %eax
leave
ret
在我看了這個問題之后,我在生成的代碼中找到了MULQ。
完整的代碼是將一個大的二進制數轉換為十億個塊,以便可以很容易地將其轉換為字符串。
C ++代碼:
for_each(TempVec.rbegin(), TempVec.rend(), [&](Short & Num){
Remainder <<= 32;
Remainder += Num;
Num = Remainder / 1000000000;
Remainder %= 1000000000;//equivalent to Remainder %= DecimalConvert
});
優化的生成裝配
00007FF7715B18E8 lea r9,[rsi-4]
00007FF7715B18EC mov r13,12E0BE826D694B2Fh
00007FF7715B18F6 nop word ptr [rax+rax]
00007FF7715B1900 shl r8,20h
00007FF7715B1904 mov eax,dword ptr [r9]
00007FF7715B1907 add r8,rax
00007FF7715B190A mov rax,r13
00007FF7715B190D mul rax,r8
00007FF7715B1910 mov rcx,r8
00007FF7715B1913 sub rcx,rdx
00007FF7715B1916 shr rcx,1
00007FF7715B1919 add rcx,rdx
00007FF7715B191C shr rcx,1Dh
00007FF7715B1920 imul rax,rcx,3B9ACA00h
00007FF7715B1927 sub r8,rax
00007FF7715B192A mov dword ptr [r9],ecx
00007FF7715B192D lea r9,[r9-4]
00007FF7715B1931 lea rax,[r9+4]
00007FF7715B1935 cmp rax,r14
00007FF7715B1938 jne NumToString+0D0h (07FF7715B1900h)
注意MUL指令5行向下。 這個生成的代碼是非常不直觀的,我知道,實際上它看起來不像編譯代碼,但是DIV非常慢〜32位div的25個周期,而現代PC上的這個圖表與MUL或IMUL相比約為75 3或4個周期)所以即使您必須添加各種額外指令,嘗試擺脫DIV也是有意義的。
我不完全理解這里的優化,但是如果你想看到使用編譯時和乘法來除常數的理性和數學解釋,請參閱本文 。
這是一個例子,編譯器利用完整的64乘64位未截斷乘法的性能和能力,而沒有向c ++編碼器顯示任何符號。
我的直覺告訴我,編譯器任意選擇IMUL
(或兩者中較快的一個),因為無論使用無符號MUL
還是有符號IMUL
這些位都是相同的。 任何32位整數乘法都是64位,跨越兩個寄存器EDX:EAX
。 溢出進入EDX
,基本上被忽略,因為我們只關心EAX
的32位結果。 使用IMUL
將根據需要簽名擴展到EDX
,但我們並不關心,因為我們只對32位結果感興趣。
正如已經解釋過的那樣,C / C ++不會將word*word to double-word
操作,這是mul
指令最適合的。 但是有些情況下你需要word*word to double-word
所以你需要擴展到C / C ++。
GCC,Clang和ICC提供了一個內置類型__int128
,您可以使用它來間接獲取mul
指令。
使用MSVC,它提供了生成mul
指令的_umul128內在函數(至少自VS 2010起)。 使用此內在函數和_addcarry_u64內在函數,您可以使用MSVC構建自己的高效__int128
類型。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.