【问题标题】:Passing scalar types by value or reference: does it matter?通过值或引用传递标量类型:重要吗?
【发布时间】:2014-01-25 18:00:22
【问题描述】:

当然,micro-optimization is stupid 可能是实践中许多错误的原因。尽管如此,我看到很多人这样做:

void function( const double& x ) {}

代替:

void function( double x ) {}

因为它被认为是“更有效率”。假设function在程序中被频繁调用,数百万次;这种“优化”是否重要?

【问题讨论】:

  • 引用传递也可用于从函数返回多个值。
  • @suspectus 当然可以,但这不是我的问题 :)
  • @suspectus 未应用 const
  • @Mgetz 你完全正确,对不起!让我阅读这篇文章,然后我可能会结束我的问题。再次抱歉,我没有找到那个..

标签: c++ reference arguments


【解决方案1】:

在这种情况下,通过引用传递本身肯定不会更有效。请注意,使用const 限定该引用并不意味着引用的对象不能更改。而且,这并不意味着函数本身不能改变它(如果裁判不是恒定的,那么它可以合法地使用const_cast摆脱那个const的函数)。考虑到这一点,很明显,按引用传递会强制编译器考虑可能的别名问题,这通常会导致在按引用的情况下生成 [显着] 效率较低的代码。

为了消除图片中可能出现的锯齿,必须在后一个版本开始时使用

void function( const double& x ) {
  double non_aliased_x = x;
  // ... and use `non_aliased_x` from now on
  ...
}

但这首先会破坏通过引用传递的建议推理。

另一种处理别名的方法是使用某种 C99 风格的 restrict 限定符

void function( const double& restrict x ) {

但同样,即使在这种情况下,通过引用传递的缺点可能会超过优点,正如其他答案中所解释的那样。

【讨论】:

    【解决方案2】:

    除非函数是内联的,并且取决于calling convention(以下假设基于堆栈的参数传递,在现代调用约定中,仅当函数具有太多 arguments*),参数的传递和使用方式有两个不同:

    • double:(可能)8 字节大值被写入堆栈并由函数按原样读取。
    • double &double *:该值位于内存中的某个位置(可能“靠近”当前堆栈指针,例如,如果它是一个局部变量,但也可能位于很远的某个地方)。一个(可能)48 字节 大指针地址(分别为 32 位或 64 位系统)存储在堆栈上,该函数需要 取消引用 strong> 读取值的地址。这还要求值在可寻址内存中,而寄存器不在。

    这意味着,当使用引用时,传递参数所需的堆栈空间可能会少一些。这不仅降低了内存需求,而且还降低了堆栈最高字节的缓存效率。使用引用时,取消引用会增加一些工作要做。

    总而言之,使用大型类型的引用(比如sizeof(T) > 32 或更多)。如果sizeof(T) > sizeof(T*),当堆栈大小和热度发挥非常重要的作用时可能已经。


    *) 如果不是这种情况,请参阅有关此问题的 cmets 和 SOReader 的答案。

    【讨论】:

    • 好吧,所有的 cmets 似乎都朝着同一个方向发展,我喜欢你的回答和 MGetz 的回答
    • 当然,这都受特定平台 ABI 的约束 - double 和指针都没有在堆栈上传递,而是在寄存器中传递,在这种情况下差异化要小得多......
    • @twalberg 当然可以。但是大多数调用约定使用堆栈作为参数。我在回答中添加了(希望)适当的评论。感谢您指出这一点。
    • @leemes 我认为如果您查看现代 ABI 的集合,您可能会感到惊讶。如果有“大量”参数,几乎所有人都会使用堆栈,但如果只有一两个参数,它们通常会在寄存器中传递——至少在桌面/服务器类型的系统上是这样;嵌入式 ABI 在这方面可能会完全不同,我猜......
    • @leemes:Win32 确实如此,但 64 位 Windows 代码只使用一种调用约定,fastcall,它最多使用 4 个寄存器来传递参数。为不同的目标编译时,GCC 使用任意数量的不同调用约定。
    【解决方案3】:

    长话短说不,尤其是在大多数通过寄存器传递标量甚至浮点类型的现代平台上。我见过的一般经验法则是 128 字节作为你应该只传递值和传递引用之间的分界线。

    鉴于数据已经存储在寄存器中这一事实,您实际上是通过要求处理器去缓存/内存来获取数据来减慢速度。这可能是一个巨大的打击,具体取决于数据所在的缓存行是否无效。

    归根结底,这实际上取决于平台 ABI 和调用约定。当优化启动时,大多数现代编译器甚至会使用寄存器来传递适合的数据结构(例如,两个短裤的结构等)。

    【讨论】:

    • @AntonTykhyy 这真的是一个判断电话的事情,我个人会保持它更小,但那就是我。不过,我在不止一个地方看到过 128 字节。
    • @Basilevs 人们忘记了他们在多任务系统上......而且由于中断或滴答结束,即使是应该靠近的堆栈也可能离得更远。
    【解决方案4】:

    在后一个示例中,您可以在函数调用期间将 4B 复制到堆栈中。存储双精度值需要 8B,而存储指针仅需要 4B(在 32b 环境中,在 64b 中需要 64b=8B,因此您不保存任何内容)或引用,它只不过是带有一点编译器支持的指针。

    【讨论】:

    • 或者相反,通过引用传递可能会强制将存储在寄存器或 FPU 堆栈中的double 复制到 CPU 堆栈。秋千和/或环形交叉路口。
    猜你喜欢
    • 2010-11-25
    • 1970-01-01
    • 2011-06-20
    • 1970-01-01
    • 2019-05-18
    • 2013-09-10
    • 2012-06-03
    • 2019-03-07
    • 1970-01-01
    相关资源
    最近更新 更多