Recent C++ compilers是否会自动检测重复的数学运算?

huangapple go评论61阅读模式
英文:

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;
    // 对这些值进行操作,例如,将它们分配到循环外的数组中
}

如果你真的是按顺序将yz放入数组,甚至可能会有利可图地使用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 &lt; 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 yand z sequentially into arrays, it might even be profitable to use whatever SIMD intrinsics your CPU allows for.

huangapple
  • 本文由 发表于 2023年5月10日 20:25:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/76218406.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定