2014-04-03 206 views
34

我在C++中做了一個簡單的程序來比較兩種方法之間的性能 - 按值傳遞和按引用傳遞。實際上,通過價值表現比通過參考更好。通過值比傳遞通過更快

結論應該是路過的值需要更少的時鐘週期(指令)

,我會很高興,如果有人能詳細爲什麼按值傳遞需要更少的時鐘週期解釋。

#include <iostream> 
#include <stdlib.h> 
#include <time.h> 

using namespace std; 

void function(int *ptr); 
void function2(int val); 

int main() { 

    int nmbr = 5; 

    clock_t start, stop; 
    start = clock(); 
    for (long i = 0; i < 1000000000; i++) { 
     function(&nmbr); 
     //function2(nmbr); 
    } 
    stop = clock(); 

    cout << "time: " << stop - start; 

    return 0; 
} 

/** 
* pass by reference 
*/ 
void function(int *ptr) { 
    *ptr *= 5; 
} 

/** 
* pass by value 
*/ 
void function2(int val) { 
    val *= 5; 
} 
+5

你可能想讀這個:[想要速度?按值傳遞](http:// cpp-next。com/archive/2009/08/want-speed-pass-by-value /) – Angew

+22

這兩個函數的行爲是不一樣的,所以它不是一個公平的比較......(另外,你傳遞一個指針,而不是C++參考...) –

+24

'function2' does * nothing *,因此它可以完全從機器代碼中省略。 –

回答

68

找出爲什麼會有差異的好方法是檢查反彙編。下面是我在我的機器上得到了與Visual Studio 2012

隨着優化標誌的結果,這兩個函數生成相同的代碼:

009D1270 57     push  edi 
009D1271 FF 15 D4 30 9D 00 call  dword ptr ds:[9D30D4h] 
009D1277 8B F8    mov   edi,eax 
009D1279 FF 15 D4 30 9D 00 call  dword ptr ds:[9D30D4h] 
009D127F 8B 0D 48 30 9D 00 mov   ecx,dword ptr ds:[9D3048h] 
009D1285 2B C7    sub   eax,edi 
009D1287 50     push  eax 
009D1288 E8 A3 04 00 00  call  std::operator<<<std::char_traits<char> > (09D1730h) 
009D128D 8B C8    mov   ecx,eax 
009D128F FF 15 2C 30 9D 00 call  dword ptr ds:[9D302Ch] 
009D1295 33 C0    xor   eax,eax 
009D1297 5F     pop   edi 
009D1298 C3     ret 

這基本上等同於:

int main() 
{ 
    clock_t start, stop ; 
    start = clock() ; 
    stop = clock() ; 
    cout << "time: " << stop - start ; 
    return 0 ; 
} 

無優化標誌,你可能會得到不同的結果。

功能(不優化):

00114890 55     push  ebp 
00114891 8B EC    mov   ebp,esp 
00114893 81 EC C0 00 00 00 sub   esp,0C0h 
00114899 53     push  ebx 
0011489A 56     push  esi 
0011489B 57     push  edi 
0011489C 8D BD 40 FF FF FF lea   edi,[ebp-0C0h] 
001148A2 B9 30 00 00 00  mov   ecx,30h 
001148A7 B8 CC CC CC CC  mov   eax,0CCCCCCCCh 
001148AC F3 AB    rep stos dword ptr es:[edi] 
001148AE 8B 45 08    mov   eax,dword ptr [ptr] 
001148B1 8B 08    mov   ecx,dword ptr [eax] 
001148B3 6B C9 05    imul  ecx,ecx,5 
001148B6 8B 55 08    mov   edx,dword ptr [ptr] 
001148B9 89 0A    mov   dword ptr [edx],ecx 
001148BB 5F     pop   edi 
001148BC 5E     pop   esi 
001148BD 5B     pop   ebx 
001148BE 8B E5    mov   esp,ebp 
001148C0 5D     pop   ebp 
001148C1 C3     ret 

函數2(不優化)

00FF4850 55     push  ebp 
00FF4851 8B EC    mov   ebp,esp 
00FF4853 81 EC C0 00 00 00 sub   esp,0C0h 
00FF4859 53     push  ebx 
00FF485A 56     push  esi 
00FF485B 57     push  edi 
00FF485C 8D BD 40 FF FF FF lea   edi,[ebp-0C0h] 
00FF4862 B9 30 00 00 00  mov   ecx,30h 
00FF4867 B8 CC CC CC CC  mov   eax,0CCCCCCCCh 
00FF486C F3 AB    rep stos dword ptr es:[edi] 
00FF486E 8B 45 08    mov   eax,dword ptr [val] 
00FF4871 6B C0 05    imul  eax,eax,5 
00FF4874 89 45 08    mov   dword ptr [val],eax 
00FF4877 5F     pop   edi 
00FF4878 5E     pop   esi 
00FF4879 5B     pop   ebx 
00FF487A 8B E5    mov   esp,ebp 
00FF487C 5D     pop   ebp 
00FF487D C3     ret 

爲什麼是按值傳遞更快(在沒有優化的情況下)?

那麼,function()有兩個額外的mov操作。讓我們來看看第一個加mov操作:

001148AE 8B 45 08    mov   eax,dword ptr [ptr] 
001148B1 8B 08    mov   ecx,dword ptr [eax] 
001148B3 6B C9 05    imul  ecx,ecx,5 

在這裏,我們取消引用指針。在function2(),我們已經有了價值,所以我們避免了這一步。我們首先將指針的地址移動到寄存器eax中。然後我們將指針的值移到寄存器ecx中。最後,我們將該值乘以五。

讓我們看看第二個加mov操作:

001148B3 6B C9 05    imul  ecx,ecx,5 
001148B6 8B 55 08    mov   edx,dword ptr [ptr] 
001148B9 89 0A    mov   dword ptr [edx],ecx 

現在,我們在倒退。我們剛剛完成了將該值乘以5,並且我們需要將該值放回到內存地址中。

由於function2()不必處理引用和取消引用指針,它會跳過這兩個額外的mov操作。

+2

+1以獲得詳細的答案。我會在適當的時候考慮這一點,也許接受它:-) –

+0

這隻有當沒有複製構造函數或析構函數的值,你正在通過值 –

+1

好工作!!!!!!! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! –

4

按值傳遞往往是小類型的非常快,因爲他們大多是比現代系統(64位)指針較小。按價值傳遞也可能會有某些優化。

作爲一般規則,按價值傳遞內建類型。

11

對一些推理: 在最熱門的機器,整數是32位,並且指針是32或64位

所以你要傳遞很多信息。

要乘以一個整數,你必須:

乘以它。

要乘以你有一個指針指向一個整數:

尊重的指針。 乘以它。

希望它足夠:)明確


我們一些更具體的東西:

由於它已經指出的那樣,你的價值函數不做任何處理的結果,而是由指針一個實際上將結果保存在內存中。爲什麼你對可憐的指針如此不公平? :(只是在開玩笑)

很難說你的基準測試的有效性是否合理,因爲編譯器會提供各種優化(當然你可以控制編譯器的自由度,但是你沒有提供有關這方面的信息)你知道,你可能會發現一個機器的指針速度更快,並且值得花費很多時間,或者是一個指針,它可能是最重要的指針,值或引用。好的,好吧,硬件有一些模式,我們做出所有這些假設,最廣泛接受的似乎是:

通過引用(或指針)傳遞簡單對象的值和更​​複雜的對象, (但是,再一次,有什麼複雜的?什麼是簡單的?隨着硬件的變化,它隨着時間而變化)

所以最近我感覺標準意見正在成爲:傳遞值和信任編譯器。這很酷。編譯器經過多年的專業技術開發和憤怒的用戶的支持,要求它始終更好。

4

在這種情況下,編譯器可能已經意識到乘法的結果並未在按值傳遞的情況下使用,並將其完全優化。沒有看到反彙編的代碼,這是不可能的。

6

當您按值傳遞時,您告訴編譯器將您傳遞的實體複製爲值。

當您通過引用傳遞時,您告訴編譯器它必須使用該引用指向的實際內存。編譯器不知道你是在嘗試優化,還是因爲參考值可能在其他某個線程中更改(例如)。它必須使用該區域的內存。

通過引用傳遞意味着處理器必須訪問該特定的內存塊。這可能是也可能不是最有效的過程,具體取決於寄存器的狀態。通過引用傳遞時,可以使用堆棧中的內存,這可以增加訪問緩存(更快)內存的機會。

最後,根據機器的體系結構和傳遞的類型,引用實際上可能大於要複製的值。複製一個32位整數涉及複製少於在64位機器上傳遞引用。

因此,只有當您需要引用(要對該值進行變更,或者因爲該值可能在別處進行了變異)或者複製引用的對象比取消引用必要的內存更昂貴時,才應該引用傳遞。

儘管最後一點並不重要,但一個好的經驗法則是做Java的工作:通過值傳遞基本類型,通過(const)引用傳遞複雜類型。

11

開銷與通過由參考:

  • 每個訪問需要一個解引用的,即,有一個以上存儲器讀

開銷與通過由值:

  • 值需要複製到堆棧或寄存器中

對於小對象,如整數,按值傳遞會更快。對於更大的對象(例如大型結構),複製會產生太多的開銷,所以通過引用傳遞會更快。

2

在本機64位平臺上,經常執行32位內存操作指令的速度較慢,因爲處理器無論如何都必須運行64位指令。如果編譯器正確完成,則32位指令在指令高速緩存中「配對」,但如果使用64位指令執行32位讀取,則會將4個附加字節複製爲填充並丟棄。總之,小於指針大小的值並不一定意味着它更快。這取決於情況和編譯器,絕對不應該考慮性能,除了複合類型的值肯定比指針大1,或者在需要絕對最佳性能的情況下一個特定的平臺,而不考慮可移植性。通過引用或按值傳遞的選擇應該僅取決於是否希望被調用的過程能夠修改傳遞的對象。如果僅僅是小於128位的類型的讀取,則按值傳遞,這是更安全的。

5

想象一下,你走進一個函數,你應該進來一個int值。函數中的代碼想要使用該int值進行操作。

按值傳遞就像進入函數一樣,當有人要求int foo值時,就把它給它們。

通過引用傳遞正在進入具有int foo值地址的函數。現在,每當有人需要foo的價值時,他們必須去查找它。每個人都會抱怨不得不取消所有這些驚人的時間。我已經在這個功能2毫秒了,我一定已經查了上千次!你爲什麼不把價值放在第一位呢?你爲什麼不通過價值?

這個比喻幫助我明白爲什麼按價值傳遞往往是最快的選擇。

+0

這個答案應該有更多upvotes :) – DoubleK