英文:
Do recent C++ compilers detect repeated mathematical operations automatically?
问题
假设我有以下代码:
const int n = fun1();
const double x = fun2();
for(int i=0; i < n; i++) {
double y = fun3(i) / std::sqrt(x);
double z = fun4(i) / std::sqrt(x);
// 对值做一些操作,例如将它们分配到循环外的数组中
}
最近的编译器能够优化成:
const int n = fun1();
const double x = fun2();
const double sqrtx = std::sqrt(x); // 仅计算一次,而不是可能的 2*n 次
for(int i=0; i < n; i++) {
double y = fun3(i) / sqrtx;
double z = fun4(i) / sqrtx;
// 对值做一些操作,例如将它们分配到循环外的数组中
}
还是值得手动进行这样的优化吗?
英文:
Let's say I have the following Code:
const int n = fun1();
const double x = fun2();
for(int i=0; i < n; i++) {
double y = fun3(i) / std::sqrt(x);
double z = fun4(i) / std::sqrt(x);
// Do something with the values, e.g., assign them to an array outside the loop
}
Do recent compilers manage to optimize it into
const int n = fun1();
const double x = fun2();
const double sqrtx = std::sqrt(x); // only computed once instead of possibly 2*n times
for(int i=0; i < n; i++) {
double y = fun3(i) / sqrtx;
double z = fun4(i) / sqrtx;
// Do something with the values, e.g., assign them to an array outside the loop
}
or is it still worth to do such optimizations by hand?
答案1
得分: 6
Clang 和 GCC 都调用 sqrt
两次,它们无法确定 std::sqrt
是否具有副作用,因此无法优化这些调用。在libc++中,Clang 使用单个 sqrtsd
指令实现 std::sqrt
,但仍然两次调用该指令。如果将示例更改为内联函数,则编译器可能会删除重复操作。所有三个编译器只进行一次平方运算。
英文:
(as of the time of writing) clang and gcc don't make this optimisation but MSVC does: https://godbolt.org/z/eKcva1heT. Clang and GCC both call sqrt
twice, it's too complex for them to determine whether std::sqrt
has side effects so they can't optimise away the calls.
With libc++ clang implements std::sqrt
with a single sqrtsd
instruction but still uses that instruction twice: https://godbolt.org/z/G5sEPYqEP.
If it was an inline function then the compiler potentially can remove duplicate operations. If we change your example to:
inline double square(double value)
{
return value * value;
}
int main()
{
const int n = fun1();
const double x = fun2();
for(int i=0; i < n; i++) {
double y = fun3(i) / square(x);
double z = fun4(i) / square(x);
std::cout << x << y;
}
}
Then all three compilers only do the square once: https://godbolt.org/z/Ys6xPcsc1
答案2
得分: 1
Alan的回答非常好。我只是在这里补充一下:
是的,即使你有一个“足够聪明的编译器”,你也应该始终“手动进行这种优化”。原因是这样可以使你的意图更明显。
另外,你应该提升除法操作。乘法操作的负担较小:
const int n = fun1();
const double x = fun2();
const double inv_sqrtx = 1 / std::sqrt(x); // 只计算一次,而不是可能的2*n次
for(int i=0; i < n; i++) {
double y = fun3(i) * inv_sqrtx;
double z = fun4(i) * inv_sqrtx;
// 对这些值进行操作,例如,将它们分配到循环外的数组中
}
如果你真的是按顺序将y
和z
放入数组,甚至可能会有利可图地使用CPU允许的SIMD指令。
英文:
Alan's answer is very good. I'm only here to supplement it:
Yes you should always "do such optimizations by hand", even if you have a "sufficiently smart compiler". The reason is that it makes your intentions more obvious.
Also, you should hoist the division too. Multiplications are less strenuous:
const int n = fun1();
const double x = fun2();
const double inv_sqrtx = 1 / std::sqrt(x); // only computed once instead of possibly 2*n times
for(int i=0; i < n; i++) {
double y = fun3(i) * inv_sqrtx;
double z = fun4(i) * inv_sqrtx;
// Do something with the values, e.g., assign them to an array outside the loop
}
If you are really dumping y
and z
sequentially into arrays, it might even be profitable to use whatever SIMD intrinsics your CPU allows for.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论