通过值或引用传递标量类型:这有关系吗



诚然,微优化是愚蠢的,可能是实践中许多错误的原因。尽管如此,我还是看到很多人在做以下事情:

void function( const double& x ) {}

而不是:

void function( double x ) {}

因为它被认为"更有效率"。说function在一个节目中被调侃的次数高达数百万次;这种"优化"有意义吗?

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

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

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

在这种情况下通过引用传递本身肯定不会更高效。请注意,使用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 ) {

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

在后一个示例中,您保存了4B在函数调用期间被复制到堆栈的时间。存储doubles需要8B,存储指针只需要4B(在32b环境中,在64b环境中需要64b=8B,所以您不保存任何内容)或引用,该引用只不过是一个具有编译器支持的指针。

除非函数是内联的,并且取决于调用约定(以下假设基于堆栈的参数传递,在现代调用约定中,只有当函数有太多参数*时才使用),否则参数的传递和使用方式有两个差异:

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

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

总之,使用大型类型的引用(比如sizeof(T) > 32或更多)。如果CCD_ 10。


*)如果不是这样的话,请参阅对此的评论和SOReader的答案。

相关内容

最新更新