2014-04-03 55 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