优化是否应用于单行函数?
Is optimization applied to single-line functions?
我不喜欢在代码中重复自己,也不想因为简单的功能而失去性能。假设 class 具有 operator+
和具有相同功能的函数 Add
(将前者视为在表达式中使用 class 的便捷方式,将后者视为 "expilicit" 的方式)
struct Obj {
Obj operator+(float);
Obj Add(float);
/* some other state and behaviour */
};
Obj AddDetails(Obj const& a, float b) {
return Obj(a.float_val + b, a.some_other_stuff);
}
Obj Obj::operator+(float b) {
return AddDetails(*this, b);
}
Obj Obj::Add(float b) {
return AddDetails(*this, b);
}
为了方便修改,这两个函数都是通过辅助函数调用实现的。因此,对接线员的任何呼叫都会发出 2 次呼叫,这并不令人愉快。
但是编译器是否足够智能以消除此类双重调用?
我用简单的 classes(包含内置类型和指针)进行了测试,优化器只是不计算不需要的东西,但它在大型系统中的表现如何(尤其是热调用) ?
如果这是发生 RVO 的地方,那么在更大的调用序列 (3-4) 中是否可以在 1 次调用中折叠它?
P.S。是的是的,过早的优化是万恶之源,但我还是想要一个答案
据我了解,现代编译器需要在您的案例中应用复制省略。根据https://en.cppreference.com/w/cpp/language/copy_elision,当你写return Obj(a.float_val + b, a.some_other_stuff)
时,构造函数调用是纯右值;返回它不会创建临时对象,因此不会发生移动或复制。
总体
是 参见https://godbolt.org/z/VB23-W第21行
生成的指令clang
movsd xmm0, qword ptr [rsp] # xmm0 = mem[0],zero
addsd xmm0, qword ptr [rip + .LCPI3_0]
它只是直接应用 AddDetails
的代码,而不是调用您的 operator+。这称为内联,甚至适用于此价值链返回调用。
详情
不仅 RVO 优化会发生在单行函数上,包括内联在内的所有其他优化都发生在 https://godbolt.org/z/miX3u1 and https://godbolt.org/z/tNaSW 上。
看看这个你可以看到 gcc 和 clang 甚至对非内联声明的代码进行了大量优化,( https://godbolt.org/z/8Wf3oR )
#include <iostream>
struct Obj {
Obj(double val) : float_val(val) {}
Obj operator+(float b) {
return AddDetails(*this, b);
}
Obj Add(float b) {
return AddDetails(*this, b);
}
double val() const {
return float_val;
}
private:
double float_val{0};
static inline Obj AddDetails(Obj const& a, float b);
};
Obj Obj::AddDetails(Obj const& a, float b) {
return Obj(a.float_val + b);
}
int main() {
Obj foo{32};
Obj bar{foo + 1337};
std::cout << bar.val() << "\n";
}
即使没有内联也看不到额外的 C-Tor 调用
#include <iostream>
struct Obj {
Obj(double val) : float_val(val) {}
Obj operator+(float);
Obj Add(float);
double val() const {
return float_val;
}
private:
double float_val{0};
static Obj AddDetails(Obj const& a, float b);
};
Obj Obj::AddDetails(Obj const& a, float b) {
return Obj(a.float_val + b);
}
Obj Obj::operator+(float b) {
return AddDetails(*this, b);
}
Obj Obj::Add(float b) {
return AddDetails(*this, b);
}
int main() {
Obj foo{32};
Obj bar{foo + 1337};
std::cout << bar.val() << "\n";
}
然而,由于编译器知道该值不会改变,因此进行了一些优化,因此让我们将 main 更改为
int main() {
double d{};
std::cin >> d;
Obj foo{d};
Obj bar{foo + 1337};
std::cout << bar.val() << "\n";
}
但是你仍然可以看到两个编译器的优化
https://godbolt.org/z/M2jaSH and https://godbolt.org/z/OyQfJI
我不喜欢在代码中重复自己,也不想因为简单的功能而失去性能。假设 class 具有 operator+
和具有相同功能的函数 Add
(将前者视为在表达式中使用 class 的便捷方式,将后者视为 "expilicit" 的方式)
struct Obj {
Obj operator+(float);
Obj Add(float);
/* some other state and behaviour */
};
Obj AddDetails(Obj const& a, float b) {
return Obj(a.float_val + b, a.some_other_stuff);
}
Obj Obj::operator+(float b) {
return AddDetails(*this, b);
}
Obj Obj::Add(float b) {
return AddDetails(*this, b);
}
为了方便修改,这两个函数都是通过辅助函数调用实现的。因此,对接线员的任何呼叫都会发出 2 次呼叫,这并不令人愉快。
但是编译器是否足够智能以消除此类双重调用?
我用简单的 classes(包含内置类型和指针)进行了测试,优化器只是不计算不需要的东西,但它在大型系统中的表现如何(尤其是热调用) ?
如果这是发生 RVO 的地方,那么在更大的调用序列 (3-4) 中是否可以在 1 次调用中折叠它?
P.S。是的是的,过早的优化是万恶之源,但我还是想要一个答案
据我了解,现代编译器需要在您的案例中应用复制省略。根据https://en.cppreference.com/w/cpp/language/copy_elision,当你写return Obj(a.float_val + b, a.some_other_stuff)
时,构造函数调用是纯右值;返回它不会创建临时对象,因此不会发生移动或复制。
总体
是 参见https://godbolt.org/z/VB23-W第21行
生成的指令clang movsd xmm0, qword ptr [rsp] # xmm0 = mem[0],zero
addsd xmm0, qword ptr [rip + .LCPI3_0]
它只是直接应用 AddDetails
的代码,而不是调用您的 operator+。这称为内联,甚至适用于此价值链返回调用。
详情
不仅 RVO 优化会发生在单行函数上,包括内联在内的所有其他优化都发生在 https://godbolt.org/z/miX3u1 and https://godbolt.org/z/tNaSW 上。
看看这个你可以看到 gcc 和 clang 甚至对非内联声明的代码进行了大量优化,( https://godbolt.org/z/8Wf3oR )
#include <iostream>
struct Obj {
Obj(double val) : float_val(val) {}
Obj operator+(float b) {
return AddDetails(*this, b);
}
Obj Add(float b) {
return AddDetails(*this, b);
}
double val() const {
return float_val;
}
private:
double float_val{0};
static inline Obj AddDetails(Obj const& a, float b);
};
Obj Obj::AddDetails(Obj const& a, float b) {
return Obj(a.float_val + b);
}
int main() {
Obj foo{32};
Obj bar{foo + 1337};
std::cout << bar.val() << "\n";
}
即使没有内联也看不到额外的 C-Tor 调用
#include <iostream>
struct Obj {
Obj(double val) : float_val(val) {}
Obj operator+(float);
Obj Add(float);
double val() const {
return float_val;
}
private:
double float_val{0};
static Obj AddDetails(Obj const& a, float b);
};
Obj Obj::AddDetails(Obj const& a, float b) {
return Obj(a.float_val + b);
}
Obj Obj::operator+(float b) {
return AddDetails(*this, b);
}
Obj Obj::Add(float b) {
return AddDetails(*this, b);
}
int main() {
Obj foo{32};
Obj bar{foo + 1337};
std::cout << bar.val() << "\n";
}
然而,由于编译器知道该值不会改变,因此进行了一些优化,因此让我们将 main 更改为
int main() {
double d{};
std::cin >> d;
Obj foo{d};
Obj bar{foo + 1337};
std::cout << bar.val() << "\n";
}
但是你仍然可以看到两个编译器的优化 https://godbolt.org/z/M2jaSH and https://godbolt.org/z/OyQfJI