不同于按值传递和常量引用传递的程序集



假设我们有这样的代码:

template<typename T>
struct StrongValue{
constexpr const T &operator()() const {
return value;
}
T &operator()(){
return value;
}
constexpr const T &get() const {
return value;
}
T &get(){
return value;
}
T value;
};
using myint = int; // try double too
using m = StrongValue<myint>;
myint sum2(const m &a, const m &b){
return a() + b();
}
myint sum2a(const m a, const m b){
return a() + b();
}
myint sum1(myint a, myint b){
return a + b;
}
int main(){
constexpr m a{5};
constexpr m b{5};
return sum2a(a, b);
}

在 clang 和 gcc 中,-O3 汇编看起来像这样:

sum2(StrongValue<int> const&, StrongValue<int> const&):
mov eax, DWORD PTR [rsi]
add eax, DWORD PTR [rdi]
ret
sum2a(StrongValue<int>, StrongValue<int>):
lea eax, [rdi+rsi]
ret
sum1(int, int):
lea eax, [rdi+rsi]
ret
main:
mov eax, 10
ret

为什么sum2是这样编译的?

这是因为编译器会更改函数签名,如果省略引用,所以不允许这样做。

这是否意味着,如果不内联,sum2sum2a贵?

为什么 sum2 是这样编译的?

这是意料之中的。对于 sum2,您将传递两个引用。引用和const是C++的东西,CPU没有它们,对于CPU来说,它们只是指针。因此,在函数中,代码必须从内存中获取两个值并将它们相加。

另外两个版本按值接收参数。

对于所有 3 个版本,编译器选择使用调用约定__regcall这就是在寄存器RSIRDI中传递这两个参数的原因。这就是其他两个版本能够在一条指令中计算结果的方式。

这是否意味着,如果不内联,sum2 比 sum2a 更昂贵?

一般来说,是的。您不应该通过 const 引用传递整数,而应该通过值传递它们。但是,确切的性能影响可以忽略不计,找出答案的唯一方法是分析。

区别在于,要sum2的参数本质上是指针,而要sum2a的参数是值。这意味着在sum2中必须取消引用指针才能获得实际值,然后可以添加这些值,而在sum2a中,您可以立即添加值。

过度使用常量引用是一个常见的错误。对于具有廉价复制的对象,通常最好按值传递参数。

相关内容

最新更新