我在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;
}
答案 0 :(得分:78)
找出原因存在差异的一个好方法是检查反汇编。以下是我使用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
function2(无优化)
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中。最后,我们将该值乘以5。
让我们看看第二个额外的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
操作。
答案 1 :(得分:15)
通过引用传递的开销:
传递值的开销:
对于小对象,例如整数,按值传递会更快。对于较大的对象(例如大型结构),复制会产生过多的开销,因此通过引用传递会更快。
答案 2 :(得分:11)
有些推理: 在大多数流行的机器中,整数是32位,指针是32或64位
所以你必须传递那么多信息。
要乘以一个整数:
乘以它。
要乘以指针所指向的整数:
推断指针。 乘以它。
希望它足够清楚:)
现在来看一些更具体的内容:
正如已经指出的那样,你的by-value函数对结果没有任何作用,但by-pointer实际上将结果保存在内存中。为什么你对糟糕的指针如此不公平? :((开玩笑)
很难说你的基准测试有多有效,因为编译器充满了各种优化。 (当然你可以控制编译器的自由度,但你还没有提供相关信息)
最后(也可能是最重要的),指针,值或引用没有与之关联的速度。谁知道,你可能会发现一台机器使用指针更快,并且很难用值,或者相反。好吧,好吧,硬件中有一些模式,我们做出所有这些假设,最广泛接受的似乎是:
按值传递简单对象,通过引用(或指针)传递更复杂的对象(但是,那么复杂的是什么?什么是简单的?随着时间的推移随着硬件的变化而变化)
所以最近我感觉标准意见正在变成:通过价值传递并信任编译器。这太酷了。编译器支持多年的专业知识开发和愤怒的用户要求它总是更好。
答案 3 :(得分:11)
想象一下,你走进一个函数,你应该进入一个int值。函数中的代码想要使用该int值进行处理。
传递值就像走进函数一样,当有人要求输入int foo值时,你只需将它赋予它们。
按引用传递是使用int foo值的地址进入函数。现在每当有人需要foo的价值时,他们必须去查找它。每个人都会抱怨所有的怪胎时间不得不取消引用foo。我现在已经在这个功能中工作了2毫秒,而且我一定要查了一千次!你为什么不首先给我价值?你为什么不通过价值?
这个类比让我明白为什么通过价值往往是最快的选择。
答案 4 :(得分:6)
当您通过值传递时,您告诉编译器复制您传递的实体的值。
当您通过引用传递时,您告诉编译器它必须使用引用指向的实际内存。编译器不知道您是在尝试优化,还是因为引用的值可能在某个其他线程中发生更改(例如)。它必须使用该内存区域。
通过引用传递意味着处理器必须访问该特定内存块。根据寄存器的状态,这可能是也可能不是最有效的过程。当您通过引用传递时,可以使用堆栈上的内存,这增加了访问缓存(更快)内存的机会。
最后,根据机器的体系结构和传递的类型,引用实际上可能大于您要复制的值。复制32位整数涉及复制少于在64位机器上传递引用。
因此,只有在需要引用时(通过改变值,或者因为值可能在其他地方发生变化),或者复制引用的对象比解除引用必要的内存更昂贵,才能通过引用进行传递。
虽然最后一点非常重要,但一个好的经验法则是做Java所做的事情:按值传递基本类型,按(const)引用传递复杂类型。
答案 5 :(得分:4)
对于小型类型,通过值传递通常非常快,因为它们中的大多数都小于现代系统(64位)上的指针。通过值传递时,可能还会进行某些优化。
作为一般规则,按值传递内置类型。
答案 6 :(得分:4)
在这种情况下,编译器可能意识到乘法的结果并未用于值传递的情况并且完全优化了它。没有看到反汇编的代码,就无法确定。
答案 7 :(得分:3)
在本机64位平台上,经常执行32位内存操作指令的速度较慢,因为处理器必须运行64位指令。如果编译器正确完成,则32位指令得到配对&#34;在指令高速缓存中,但如果使用64位指令执行32位读取,则将另外的字节复制为填充,然后丢弃。简而言之,值小于指针大小并不一定意味着它更快。这取决于情况和编译器,并且绝对不应该考虑性能,除了复合类型,其值肯定比指针大1,或者在需要绝对最佳性能的情况下一个不考虑可移植性的特定平台。通过引用或按值传递之间的选择应仅取决于您是否希望被调用过程能够修改传递的对象。如果只读取小于128位的类型,按值传递,则更安全。