C++:为啥对于内置(即类 C)类型,按值传递通常比按引用传递更有效
Posted
技术标签:
【中文标题】C++:为啥对于内置(即类 C)类型,按值传递通常比按引用传递更有效【英文标题】:C++: Why pass-by-value is generally more efficient than pass-by-reference for built-in (i.e., C-like) typesC++:为什么对于内置(即类 C)类型,按值传递通常比按引用传递更有效 【发布时间】:2011-07-17 20:09:18 【问题描述】:正如标题所示
【问题讨论】:
【参考方案1】:编译器供应商通常会将引用实现为指针。指针的大小往往与许多内置类型相同或更大。对于这些内置类型,无论您是按值传递还是通过引用传递,都将传递相同数量的数据。 在函数中,为了获取实际数据,您需要取消引用这个内部指针。这可以向生成的代码添加一条指令,并且您还将拥有 两个 可能不在缓存中的内存位置。差异不会太大 - 但可以在紧密的循环中进行测量。
编译器供应商可以选择在用于内置类型时忽略 const 引用(有时也包括非 const 引用) - 这一切都取决于编译器在处理函数及其调用者时可用的信息.
【讨论】:
值得注意的是,对于内联函数,按引用传递有时比按值传递要快一些,因为可以简单地将传入的左值替换为参数。在某些情况下,当编译器无法改变语义时,它可能能够替换传递引用,但要知道这种替换何时会改变代码语义并不总是那么容易。【参考方案2】:对于 int、char、short 和 float 等 pod 类型,数据的大小与传入以引用实际数据的地址相同(或更小)。在引用的地址查找值是不必要的步骤,并且会增加额外的成本。
例如,取如下函数foo
和bar
void foo(char& c) ...
void bar(char c) ...
当foo
被调用时,地址将通过 32 位或 64 位的值传递,具体取决于您的平台。当您在 foo
中使用 c
时,您需要查找传入地址中保存的数据的值。
当调用bar
时传入一个char大小的值,没有地址查找开销。
【讨论】:
通常较小类型的实际传递数据将对应于 CPU 的本机字长。 @Erik: 当它没有(char
)时,它不是提升为更广泛的类型(16
位)以便通过寄存器传递吗?
@Matthieu M.:对于寄存器,它通常也被提升为全尺寸——在 x86 32 机器上,它会被传入,例如EAX 不是 AX 或 AH/AL,EAX 更快。当然,这一切都取决于编译器【参考方案3】:
在实践中,C++ 实现通常通过在底层传递一个指针来实现传递引用(假设调用不是内联的)。
因此,没有什么聪明的机制可以让按引用传递更快,因为传递指针并不比传递小值快。一旦你在函数中,按值传递也可以从更好的优化中受益。例如:
int foo(const int &a, int *b)
int c = a;
*b = 2;
return c + a;
就编译器所知,b
指向a
,这称为“别名”。如果a
是按值传递的,则此函数可以优化为等效于*b = 2; return 2*a;
。在现代 CPU 的指令流水线中,这可能更像是“开始 a 加载,开始 b 存储,等待 a 加载,乘以 2,等待 b 存储,返回”,而不是“开始 a 加载,开始 b 存储” ,等待 a 加载,等待 b 存储,开始加载,等待 a 加载,将 a 添加到 c,返回”,然后您开始了解为什么混叠的可能性会对性能产生重大影响。在某些情况下,如果不一定会在这一方面产生巨大影响。
当然,别名仅在它改变函数对某些可能输入的效果的情况下才会阻碍优化。但仅仅因为您对该函数的意图是别名不应影响结果,并不一定意味着编译器可以假设它不会:有时实际上,在您的程序中,不会发生别名,但编译器不会不知道。并且不必有第二个指针参数,只要你的函数调用优化器“看不到”的代码,它就必须假设任何引用都可以改变。
【讨论】:
以上是关于C++:为啥对于内置(即类 C)类型,按值传递通常比按引用传递更有效的主要内容,如果未能解决你的问题,请参考以下文章