假设我要传递POD对象以用作const参数。我知道对于int和double值这样的简单类型,由于存在引用开销,因此比const引用要好。但是以什么大小作为参考值得?
mapbutton
或
struct arg
{
...
}
void foo(const arg input)
{
// read from input
}
即我应该从哪种大小的arg结构开始使用后一种方法?
我还应该提到,我在这里不是在谈论复制省略。让我们假设它不会发生。
答案 0 :(得分:3)
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;
}
生成的assembly:
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;
}
生成的assembly:
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
中。
真的重要吗?
通常不会。如果您关心某个特定功能的性能,那么它可能会被频繁调用,因此它是 small 。因此,很可能会 内联 。
让我们尝试调用上述两个功能:
int test(int x)
{
arg a {x, x};
return foo1(a) + foo2(a);
}
生成的assembly:
test(int):
lea eax, [0+rdi*4]
ret
Voilà。现在都没事了。编译器将这两个函数内联并合并为一条指令!
答案 1 :(得分:1)
一条合理的经验法则:如果类的大小等于或小于指针的大小,则复制可能会快一点。
如果班级规模稍大,则可能很难预测。差异通常很小。
如果班级人数庞大,那么复制的速度可能会变慢。就是说,要点是没有意义的,因为在实践中,庞大的对象不能自动存储,因为它是受限制的。
如果函数是内联展开的,则可能没有任何区别。
要了解某个程序在特定系统上是否比另一程序快,并且首先发现差异是否很大,可以使用探查器。
答案 2 :(得分:1)
除了其他响应之外,还存在优化方面的问题。
由于是引用,因此编译器无法知道引用是否指向可变的全局变量。在调用任何源无法用于当前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);
}
如果变量是局部变量,则编译器将简单地重用寄存器中已经存在的值。
这里有一个compiler explorer example,显示仅在变量为局部变量时才应用优化。
这就是为什么“一般建议”将为您提供良好的性能,但不会为您提供最佳性能的原因。如果您真正关心代码的性能,则必须确定并分析代码。