在寫代碼的時候,加法快還是乘法快還是都一樣?
比如;
int sum=0;for(i=1;i&<10;i++)sum+=2;和sum=2*10;哪個更快?
一般來說乘法要比加法慢。但是僅限於一條乘法指令和加法指令相比。你這種編譯器如果沒有優化,肯定是比乘法慢的。
網上能查到的資料來看,8051計算一條加法指令需要一個周期,乘法指令需要四個。Intel的話,乘法指令耗時一般是加法指令的十幾倍(根據一篇名為Intel Assemble Instruction Set的文章查到的,可能不準,等有時間查查最新的手冊才能知道)。而且,CPU計算乘法並不是展開按照加法計算,那樣有點傻。計算機組成原理會介紹一種Booth乘法器。這種乘法器計算主要靠比較,位移和加減。比起實際人類計算乘法時候用的豎式計算方法要更簡單一些。StackOverflow上對這一類問題基本上只有兩個回答:
- 讀手冊
- 說不清
原理上說,單條乘法指令比加法指令慢,但差別在縮小,從大約10:1降到了3:1(Core 2、AMD K8及以後)。具體改進不清楚,希望有牛人講講。
數據來源:https://gmplib.org/~tege/x86-timing.pdfhttp://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf (表C-19a)先不考慮編譯器。用簡單加法循環模擬乘法肯定比一次乘法慢很多。以問題中的代碼為例(更正i=0),基本的操作有:- 十次加法
- 十次比較(包括一次misprediction)
- 十次遞增
- 兩次清零
總延遲肯定超過十個cycles,如果CPU沒有loop counter(Patent US5909573)或者類似優化的話,單是一次misprediction的代價就夠吃一壺了。當然,實際的情況(考慮亂序執行和其他micro-architecture級別的優化之後)不會像看起來這麼糟糕,但肯定是慢。
問題是,現代的編譯器對這一類簡單問題的優化非常到位,例如:
- Dead code elimination:sum的結果沒有用到就不算了。這個技術極其普遍並且聰明,在寫性能測試代碼的時候要十分小心。
- Constant folding:2 * 20這樣的常量表達式會在編譯時算好。
- Loop unwinding:循環會被展開以減少循環次數;如果循環次數在編譯期可確定,循環可能被完全展開。結果就像 @王濱 提到的一樣,編譯器直接給個答案(0x14)返回。
只要打開O3,兩種寫法的編譯結果是一樣的。
建議不要對代碼做任何不必要的手工優化,以增加可讀性為最高目標,把臟活累活交給編譯器和CPU。如果寫的是C語言的話,真的不用糾結這個問題,按照可讀性最好的來寫。
編譯器比大部分程序員都聰明(在優化指令上)。手寫的彙編大部分趕不上編譯器生成的指令。比如像這樣的一段int main()
{
int sum;
sum = 10;
sum *= 3;
return sum;
}
gcc -O0 編譯出來以後是這個樣子的
Dump of assembler code for function main:
0x00000000004004ec &<+0&>: push rbp
0x00000000004004ed &<+1&>: mov rbp,rsp
0x00000000004004f0 &<+4&>: mov DWORD PTR [rbp-0x4],0xa
0x00000000004004f7 &<+11&>: mov edx,DWORD PTR [rbp-0x4]
0x00000000004004fa &<+14&>: mov eax,edx
0x00000000004004fc &<+16&>: add eax,eax
0x00000000004004fe &<+18&>: add eax,edx
0x0000000000400500 &<+20&>: mov DWORD PTR [rbp-0x4],eax
0x0000000000400503 &<+23&>: mov eax,DWORD PTR [rbp-0x4]
0x0000000000400506 &<+26&>: pop rbp
0x0000000000400507 &<+27&>: ret
End of assembler dump.
如果是sum *= 2; 的話,就是這個樣子
Dump of assembler code for function main:
0x00000000004004ec &<+0&>: push rbp
0x00000000004004ed &<+1&>: mov rbp,rsp
0x00000000004004f0 &<+4&>: mov DWORD PTR [rbp-0x4],0xa
0x00000000004004f7 &<+11&>: shl DWORD PTR [rbp-0x4],1
0x00000000004004fa &<+14&>: mov eax,DWORD PTR [rbp-0x4]
0x00000000004004fd &<+17&>: pop rbp
0x00000000004004fe &<+18&>: ret
End of assembler dump.
看那行shl,被當作 sum &<&<= 1; 編譯了。
什麼,你要看-O3?int main()
{
int i, sum = 0;
for(i=0; i&<10; i++) {
sum += 2;
}
return sum;
}
這一段gcc -O3編譯出來是這個結果
Dump of assembler code for function main:
0x0000000000400400 &<+0&>: mov eax,0x14
0x0000000000400405 &<+5&>: ret
End of assembler dump.
媽蛋,循環呢?加法呢?
全被編譯器優化掉了。一定要比較IMUL和多個ADD+JMP組合(循環)之間的性能的話,這超出我的能力範圍了,期待大神。在九十年代奔騰處理器出現之前是加法比乘法快,但是奔騰處理器及之後的處理器,這兩種操作是一樣快的。雖說x86是這樣,但是其他架構的處理器就不清楚了。
乘法快,而且快很多。
cpu計算乘法是用Booth演算法(Booth演算法_百度百科),比直接用加法循環快的不只一個數量級(當然數小的時候不一定,但是可以忽略無計)
說一種比較簡單的情況,253*241,用題目的循環的方法要算240次加法,用乘法器就是計算 11111101
*11110001
——————————————————————
11111101
00000000
00000000
00000000
11111101
11111101
11111101
11111101
——————————————————————
1110111000101101
80386的數據,IA32的太殘暴了沒找到指令集的數據。
Clocks里的r/m是從寄存器取值或內存的周期數。
顯然乘法快,你這個位數乘的加法算算還可以,稍微上百就比乘法慢多了。而且這種優化也沒什麼必要,編譯的時候加個-O3選項最後編譯出來就是movl $0x14,%eax 人都直接幫你算好了·····
現代帶乘法器的CPU肯定乘法比循環加法快。比如某結構乘加只要一個鍾。然而如果以2為底的乘除,那就移位快。
做乘法快不快和CPU有關,在有乘法器的CPU中,肯定是直接做乘法快,在沒乘法器的CPU中,像早期的8051,乘法會被翻譯為加法再處理。另外,用C描述這個問題,即使是在沒有乘法器的CPU中,用加法和用乘法所耗的時間仍然可能不一樣,具體要看可執行程序的反彙編後怎樣寫的。再者,和你的乘數和除數有關,乘除2的指數次冪,有的編譯器直接翻譯成移位操作。
跟處理器型號有關,如果處理器帶有乘法器的話,明顯是寫sum=2*10更快;如果不帶有乘法器,如果編譯器沒有特別的優化的話,兩種速度一樣快,因為,乘法將會被展開成循環相加。但寫程序要考慮到移植性,因此個人認為寫成第二種較好。
一般來說加法快因為大部分的cpu的乘法和加法的指令都是1~2個周期,當然具體還和操作數的位寬有關在lz的例子里用到了循環,額外增加了不少指令開銷 如果單就lz的例子來說,有可能兩者沒什麼差別,因為某些編譯器會優化立即數運算和展開for循環,不需要在運行時由cpu計算結果,而是在編譯時直接計算結果,所以有可能在運行時兩者的執行時間是一樣的
做了測試了,在I7處理器32G內存的筆記本上,不論是在VM虛擬機中還是在WIN10下,二者執行速度基本是一樣的。在我的i7,4核8線程中執行測試 ,測試平台為最新版官方的JDK,java+eclipse
----------關於double與float的執行效率,測試結果為: 這是你在UBUNTU 16.04 下VM虛擬機里的WIN7 64位,16G分配內存,8核下下測試的結果。 執行浮點加法用掉的時間double=1.4337秒,float=0.7241秒,同樣是在40000*40000的數據量下測試結果,很明顯float數據運行的更快,快近一倍!。對於加法,你在WIN10非虛擬機的環境下也測試了,同樣的電腦,測試表明,速度較上面的VM配置要慢近三分之一,數據如下,double=1.9秒,float=1.0秒,這一點出乎預料,原以為在VM中會更慢,
實際反而快三分之一,你猜測,這可能與底層平台VMware使用的是LINUX有關係。 對於乘法你也進行了測試,結果也出乎預料,乘法執行的速度與加法相近,沒有明顯偏小。VM下結果為,float=0.71,double=1.38,WIN10下測試結果:float=1.05,double=1.90測試結果,平均每個double佔9個位元組,float佔用4.6個位元組為什麼乘法執行時間和加法是基本一樣 ?沒有拿C語言做測試。
----------------------------------------下面為乘法double的測試代碼----------------------- Random ra =new Random(); int dataLength=40000; ArrayList&}
Debug.plnTime("test memery double data inited"); Debug.sleep(10000);//休息10秒鐘,看內存佔用變化。 PerformanceTest p=new PerformanceTest("For double"); Debug.plnTime("test double add action start..."); p.startTest(); for(int i=0;i&p.endTest();
Debug.plnTime("test double add action end,time used="+p.getTotalUsedTime());推薦閱讀:
※軟體乘法和硬體乘法哪個效率高?為什麼?
※對於學生黨什麼編程語言比較適合?
※如何評價"Null reference - my billion-dollar mistake"?
※位運算有什麼奇技淫巧?
※非同步可重入函數與線程安全函數等價嗎?