0

我想将一个小次数的多项式 (2-5) 应用于长度可以在 50 到 3000 之间的向量,并尽可能高效地执行此操作。示例:例如,我们可以取函数:(1+x^2)^3,当x>3时,当x<=3时为0。对于双元素向量,这样的函数将执行 100k 次。每个向量的大小可以在 50 到 3000 之间。

一种想法是使用 Eigen: Eigen::ArrayXd v; 然后简单地应用一个仿函数: v.unaryExpr([&](double x) {return x>3 ? std::pow((1+x*x), 3.00) : 0.00;});

尝试使用 GCC 9 和 GCC 10,我发现这个循环没有被矢量化。我确实手动对其进行了矢量化,结果发现增益比我预期的要小得多(1.5 倍)。我还用逻辑 AND 指令替换了条件,基本上执行两个分支并在 x<=3 时将结果归零。我认为收益主要来自缺乏分支错误预测。

一些考虑 因素有多种因素在起作用。首先,我的代码中有 RAW 依赖项(使用内在函数)。我不确定这如何影响计算。我用 AVX2 编写了我的代码,所以我期待 4 倍的增益。我认为这起到了一定的作用,但我不能确定,因为 CPU 有无序处理。另一个问题是我不确定我尝试编写的循环的性能是否受内存带宽的限制。

问题 如何确定内存带宽或管道危害是否影响此循环的实现?我在哪里可以学习更好地矢量化这个循环的技术?Eigenr MSVC 或 Linux 中是否有用于此目的的好工具?我使用的是 AMD CPU,而不是 Intel。

4

1 回答 1

2

您可以使用 修复 GCC 错过的优化-fno-trapping-math,这实际上应该是默认设置,因为-ftrapping-math它甚至不能完全正常工作。使用该选项可以很好地自动矢量化:https ://godbolt.org/z/zfKjjq 。

#include <stdlib.h>

void foo(double *arr, size_t n) {
    for (size_t i=0 ; i<n ; i++){
        double &tmp = arr[i];
        double sqrp1 = 1.0 + tmp*tmp;
        tmp = tmp>3 ? sqrp1*sqrp1*sqrp1 : 0;
    }
}

它避免了三元组一侧的乘法,因为它们可能引发 C++ 抽象机不会引发的 FP 异常。

您希望使用三元以外的立方编写它应该让 GCC 自动矢量化,因为在源代码中没有任何 FP 数学运算是有条件的。但它实际上并没有帮助:https ://godbolt.org/z/c7Ms9G GCC 的默认值-ftrapping-math仍然决定在输入上分支以避免所有 FP 计算,可能不会引发 C++ 抽象机的溢出(无限)异常会提出。如果输入为 NaN,则无效。这就是我的意思是-ftrapping-math不工作。(相关:如何强制 GCC 假设浮点表达式为非负数?


Clang 也没有问题:https ://godbolt.org/z/KvM9fh 我建议clang -O3 -march=native -ffp-contract=fast在 FMA 可用时使用跨语句获取 FMA。

(在这种情况下,在一个表达式内 -ffp-contract=on收缩就足够了1.0 + tmp*tmp,但如果您需要避免例如 Kahan 求和,则不能跨语句收缩。clang 默认显然是-ffp-contract=off,给出单独的 mulpd 和 addpd)


当然,您需要避免std::pow使用小整数指数。编译器可能不会将其优化为仅 2 个乘法,而是调用一个完整的pow函数。

于 2020-08-14T08:20:14.117 回答