在寫代碼的時候,加法快還是乘法快還是都一樣?

比如;

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.pdf

http://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 *= 3; 被拆成了1條mov和兩條add,沒有用乘法。(這還是-O0)

如果是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

只需要算8次加法。要是數更大,比如一百萬,差別就更大了。與編譯器優化什麼的無關,跟這個差距相比都只是很小的數字。


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& datas=new ArrayList&();

Debug.plnTime("start test memery double");

for(int i=0;i& double[] d=new double[dataLength];

for(int ii=0;ii& datas.add(d);

}

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& double[] d=datas.get(i);

for(int ii=0;ii& }

p.endTest();

Debug.plnTime("test double add action end,time used="+p.getTotalUsedTime());


推薦閱讀:

軟體乘法和硬體乘法哪個效率高?為什麼?
對於學生黨什麼編程語言比較適合?
如何評價"Null reference - my billion-dollar mistake"?
位運算有什麼奇技淫巧?
非同步可重入函數與線程安全函數等價嗎?

TAG:中央處理器CPU | C編程語言 | 編譯器 |