诚然,微优化是愚蠢的,可能是实践中许多错误的原因。尽管如此,我还是看到很多人在做以下事情:
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 *
:该值位于内存中的某个位置(可能"靠近"当前堆栈指针,例如,如果它是局部变量,但也可能位于远处)。(可能)4或8字节大指针地址(分别为32位或64位系统)存储在堆栈上,函数需要取消引用地址来读取值。这也要求值在可寻址内存中,而寄存器不是
这意味着,在使用引用时,传递参数所需的堆栈空间可能会少一点。这不仅降低了内存需求,还降低了堆栈中最顶层字节的缓存效率。当使用引用时,取消引用会增加一些要做的工作。
总之,使用大型类型的引用(比如sizeof(T) > 32
或更多)。如果CCD_ 10。
*)如果不是这样的话,请参阅对此的评论和SOReader的答案。