[英]Access through reference overhead vs copy overhead
假设我要传递POD对象以用作const参数。 我知道对于int和double值这样的简单类型,由于存在引用开销,因此比const引用要好。 但是以什么大小作为参考值得?
struct arg
{
...
}
void foo(const arg input)
{
// read from input
}
要么
void foo(const arg& input)
{
// read from input
}
即,我应该以哪种大小的arg结构开始使用后一种方法?
我还应该提到,我这里不是在谈论复制省略。 让我们假设它不会发生。
TL; DR:这在很大程度上取决于目标体系结构,编译器和调用函数的上下文。 如果不确定,请分析并手动检查生成的代码。
如果内联函数,则在两种情况下,良好的优化编译器都可能会发出完全相同的代码。
但是,如果未内联函数,则大多数C ++实现中的ABI都要求传递const&
参数作为指针。 这意味着该结构必须存储在RAM中,这样才能获得其地址。 这可能会对小对象的性能产生重大影响。
让我们以x86_64 Linux G ++ 8.2为例...
具有2个成员的结构:
struct arg
{
int a;
long b;
};
int foo1(const arg input)
{
return input.a + input.b;
}
int foo2(const arg& input)
{
return input.a + input.b;
}
生成的程序集 :
foo1(arg):
lea eax, [rdi+rsi]
ret
foo2(arg const&):
mov eax, DWORD PTR [rdi]
add eax, DWORD PTR [rdi+8]
ret
第一个版本完全通过寄存器传递结构,第二个版本通过堆栈传递。
现在让我们尝试3个成员 :
struct arg
{
int a;
long b;
int c;
};
int foo1(const arg input)
{
return input.a + input.b + input.c;
}
int foo2(const arg& input)
{
return input.a + input.b + input.c;
}
生成的程序集 :
foo1(arg):
mov eax, DWORD PTR [rsp+8]
add eax, DWORD PTR [rsp+16]
add eax, DWORD PTR [rsp+24]
ret
foo2(arg const&):
mov eax, DWORD PTR [rdi]
add eax, DWORD PTR [rdi+8]
add eax, DWORD PTR [rdi+16]
ret
尽管使用第二个版本仍然会比较慢,因为使用第二个版本会需要将地址放入rdi
因此不再那么多。
真的有那么重要吗?
通常不会。 如果您关心某个特定功能的性能,那么它可能会被频繁调用,因此它很小 。 这样,很可能会内联 。
让我们尝试调用上面的两个函数:
int test(int x)
{
arg a {x, x};
return foo1(a) + foo2(a);
}
生成的程序集 :
test(int):
lea eax, [0+rdi*4]
ret
瞧。 现在都没事了。 编译器将两个函数内联并合并为一条指令!
合理的经验法则:如果类的大小等于或小于指针的大小,则复制可能会快一些。
如果班级人数稍大,则可能很难预测。 差异通常很小。
如果班级人数众多,则复制可能会变慢。 就是说,要点是没有意义的,因为在实践中大型对象无法自动存储,因为它是受限制的。
如果将函数内联展开,则可能没有任何区别。
要查明某个程序在特定系统上是否比另一个程序快,以及两者之间的区别是否显着,可以使用探查器。
除了其他响应之外,还存在优化方面的问题。
由于它是引用,因此编译器无法知道引用是否指向可变的全局变量。 当调用任何源无法用于当前TU的函数时,编译器必须假定变量可能已被突变。
例如,如果您有一个if依赖于Foo
的数据成员,则调用一个函数,然后使用相同的数据成员,则将强制编译器输出两个备用负载,而如果该变量是局部变量,则它知道不能在其他地方变异。 这是一个例子:
struct Foo {
int data;
};
extern void use_data(int);
void bar(Foo const& foo) {
int const& data = foo.data;
// may mutate foo.data through a global Foo
use_data(data);
// must load foo.data again through the reference
use_data(data);
}
如果变量是局部变量,则编译器将简单地重用寄存器中已经存在的值。
这是一个编译器浏览器示例 ,该示例显示仅在变量为局部变量时才应用优化。
这就是为什么“一般建议”将为您提供良好的性能,但不会为您提供最佳性能的原因。 如果您真正关心代码的性能,则必须确定并分析代码。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.