【问题标题】:Performance of pow(x,3.0f) vs x*x*x?pow(x,3.0f) 与 x*x*x 的性能?
【发布时间】:2021-05-14 22:47:12
【问题描述】:

下面的程序...

int main() {
    float t = 0;
    for (int i = 0; i < 1'000'000'000; i++) {
        const float x = i;
        t += x*x*x;
    }
    return t;
}

...在我的机器上完成大约需要 900 毫秒。而……

#include <cmath>

int main() {
    float t = 0;
    for (int i = 0; i < 1'000'000'000; i++) {
        const float x = i;
        t += std::pow(x,3.0f);
    }
    return t;
}

...大约需要 6600 毫秒才能完成。

我有点惊讶优化器没有内联 std::pow 函数,因此两个程序生成相同的代码并具有相同的性能。

有什么见解吗?您如何解释 5 倍的性能差异?

作为参考,我在 Linux x86 上使用 gcc -O3

更新:(C 版)

int main() {
    float t = 0;
    for (int i = 0; i < 1000000000; i++) {
        const float x = i;
        t += x*x*x;
    }
    return t;
}

...在我的机器上完成大约需要 900 毫秒。而……

#include <math.h>

int main() {
    float t = 0;
    for (int i = 0; i < 1000000000; i++) {
        const float x = i;
        t += powf(x,3.0f);
    }
    return t;
}

...大约需要 6600 毫秒才能完成。

更新 2

以下程序:

#include <math.h>

int main() {
    float t = 0;
    for (int i = 0; i < 1000000000; i++) {
        const float x = i;
        t += __builtin_powif(x,3.0f);
    }
    return t;
}

像第一个程序一样在 900 毫秒内运行。

为什么pow 不内联到__builtin_powif ?

更新 3:

使用-ffast-math以下程序:

#include <math.h>
#include <iostream>

int main() {
    float t = 0;
    for (int i = 0; i < 1'000'000'000; i++) {
            const float x = i;
            t += powf(x, 3.0f);
    }
    std::cout << t;
}

运行时间为 227 毫秒(x*x*x 版本也是如此)。每次迭代需要 200 皮秒。使用-fopt-info 它说optimized: loop vectorized using 16 byte vectors 和optimized: loop with 2 iterations completely unrolled 所以我猜这意味着它对SSE 进行4 批迭代并一次进行2 次迭代流水线(一次总共进行8 次迭代),或者类似的东西?

【问题讨论】:

  • 如果你使用pow(x, 3)会发生什么?
  • 您对std::pow 的调用使用浮点值作为指数(您可以用它计算根),而x*x*x 则更简单。
  • 如您所知,请不要同时标记 C 和 C++,除非问题确实与两种语言有关(它们之间的翻译或类似语言)。您的代码是 C++ 特定的,不能使用 C 编译器构建。
  • 既然您使用的是 GCC,请尝试t += __builtin_powif(x, 3) -- 结果应该很有启发性。
  • 试试-ffast-math。

标签: c++ c gcc


【解决方案1】:

% 假设您的编译器选择只在共享库中调用pow,如https://godbolt.org/z/re3baK(不带-ffast-math)

我没有看pow(float, float)是如何实现的,但是看到了一些点。

  1. x*x*x 是内联的,而 pow 不能像在共享库中那样 - 函数调用开销差异
  2. 指数3.0 是否为常数?如果编译器知道某些东西是不变的,它可能会生成更高效的代码
    • x*x*x:只生成两次浮点值乘法的程序集
    • pow :这必须考虑到所有指数值,因此它可能具有通用代码(效率较低,可能包括循环)

【讨论】:

  • 编译器可以内联或优化pow,因为C标准规定了它的行为方式,而外部标识符pow是为此目的而保留的,因此编译器知道它需要做什么。此外,编译器和库可以捆绑为一个 C 实现的一部分,因此实现可以完全控制编译器生成的代码和pow 的实现。
  • 好吧,我在回答“为什么会有 5 倍的差异?”这个问题。并且使用-O3(没有-ffast-math)它确实调用了pow 函数并且速度很慢。 godbolt.org/z/Kx71W7 如果我错过了什么,请告诉我。
  • 如果特定编译器在特定情况下没有内联pow,那么原因不是因为pow在共享库中而不能内联;原因是编译器的设计没有内联 pow。这可能会因其他考虑而变得复杂,但这不是 C 标准的要求。
  • 我明白你的意思,我并不是说我所说的是标准的一部分。只是想分析 OP 要求的显着性能差异。
  • 重点是“pow 不能像在共享库中那样”是错误的。无论您打算分析或传达什么,此答案都会提供虚假信息,因此是有害的。事实上,GCC and Clang do replace some calls to pow with inline code,如果启用了-ffast-math,则包括pow(x, 3)。第二项,关于常数,也错误地陈述了情况;问题中的代码有一个硬编码的3.0f,所以它是一个常数;此答案中有关具有“通用代码”或考虑所有指数值的文本不适用。
【解决方案2】:

关于gcc builtins 的文档页面是明确的(强调我的):

内置函数:double __builtin_powi(double, int)

返回第一个参数的第二个幂。 与 pow 函数不同,不保证精度和舍入。

内置函数:float __builtin_powif(float, int)

类似于 __builtin_powi,除了参数和返回类型是浮点数。

由于__builtin_powif 具有与单纯产品相当的性能,这意味着额外的时间用于pow 所需的控件以保证精度和舍入。

【讨论】:

  • 好的,那么x*x*x 和powf(x,3) 对于某些 x 值会产生不同的结果?如果 powf 更精确的话,我猜这是有道理的。
  • @AndrewTomazos:我认为是的。唯一真正走得更远的方法是检查 gcc 库中pow/powf 的源代码。
  • 有趣的是,这种行为在 C++11 中发生了变化。使用__builtin_powif 的std::pow 的libstdc++ 定义仅发生在#if __cplusplus &lt; 201103L 中。
  • @TravisGockel 我相信这是由于std::pow (7) “如果任何参数具有整数类型,它将被强制转换为double”。 Here 仅列出浮点重载。另外:stackoverflow.com/a/6626234/4944425
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-04-29
  • 2017-07-14
  • 1970-01-01
  • 1970-01-01
  • 2020-04-19
  • 2010-09-15
  • 1970-01-01
相关资源
最近更新 更多