【问题标题】:C++11 in conjunction with OpenMP gives slower executableC++11 与 OpenMP 结合使用可提供较慢的可执行文件
【发布时间】:2015-12-22 15:37:08
【问题描述】:

我正在尝试学习 OpenMP,并想学习使用 OpenMP 进行加速。为此,我编写了如下小程序:

#include <vector>
#include <cmath>

int main() {
    static const unsigned int testDataSize = 1 << 28;

    std::vector<double> a (testDataSize), b (testDataSize);

    for (int i = 0; i < testDataSize; ++i) {
        a [i] = static_cast<double> (23 ^ i) / 1000.0;
    }
    b.resize(testDataSize);

    #pragma omp parallel for
    for (int i = 0; i < testDataSize; ++i) {
        b [i] = std::pow(a[i], 3) * std::exp(-a[i] * a[i]);
        b [i] += std::pow(a[i], 5) * std::exp(-a[i] * a[i]);
        b [i] += std::pow(a[i], 7) * std::exp(-a[i] * a[i]);
        b [i] += std::pow(a[i], 9) * std::exp(-a[i] * a[i]);
        b [i] += std::pow(a[i], 11) * std::exp(-a[i] * a[i]);
        b [i] += std::pow(a[i], 13) * std::exp(-a[i] * a[i]);
        b [i] += std::pow(a[i], 15) * std::exp(-a[i] * a[i]);
        b [i] += std::pow(a[i], 17) * std::exp(-a[i] * a[i]);
        b [i] += std::pow(a[i], 19) * std::exp(-a[i] * a[i]);
        b [i] += std::pow(a[i], 21) * std::exp(-a[i] * a[i]);
    }

    return 0;
}

我编译了上面的代码,不管有没有 -std=c++11 指令。我注意到的是,当我使用 -std=c++11 指令时,我的代码运行速度比不使用它慢了大约 8 倍。我在 Linux Debian 系统上使用 -O3 和 gcc 版本 4.9.2。此外,当我比较不使用 OpenMP 的执行时间时,我确实注意到速度差异。因此,在我看来,-std=c++11 存在问题,而不是 OpenMP。

详细来说,我获得了以下执行时间(使用 Linux time 命令测量)

使用 OpenMP 和 -std=c++11 编译: 35.262s

仅使用 OpenMP 编译: 5.875s

仅使用 -std=c++11 编译: 2m12

没有 OpenMP 和 -std=c++11 的编译: 23.757s

使用-std=c++11时执行时间慢很多的原因是什么?

非常感谢任何帮助或建议!


我已经标记了以我的拙见为最佳答案的内容。在跟进 oLen 的回答时,我制作了自己的 pow(double, int) 函数,如下所示:

double my_pow(double base, int exp) {
    double result = 1.0;

    while (exp) {
        if (exp & 1)
            result *= base;
        exp >>= 1;
        base *= base;
    }

    return result;
}

我不确定这是否是计算某个基数的整数幂的最有效方法,但是使用此函数在使用或不使用 std=c++11 进行编译时,在计算效率方面我得到完全相同的结果完全符合 oLen 的回答。

【问题讨论】:

  • 尝试把它扔到 Godbot 并比较生成的程序集?
  • 您的问题是什么? “为什么 c++11 这么慢?”。您正在使用哪些选项?大会是什么样子的?
  • 这是一个程序,不是脚本。
  • @erip:确实;感谢您的更正。我已将上述问题中的脚本更改为程序。

标签: c++ c++11 openmp


【解决方案1】:

原因是没有-std=c++11的版本使用std::pow(double,int),这在C++11中显然没有,而且比std::pow(double,double)快。如果将整数(3、5 等)替换为双精度数(3.0、5.0 等),您将获得相同的速度。

编辑: 以下是我对 g++ 版本 4.8.4 的时间安排:
原始版本:
-O3 -fopenmp : 10.678 秒
-O3 -fopenmp -std=c++11 : 36.994 秒
在整数后添加“.0”:
-O3 -fopenmp : 36.679 秒
-O3 -fopenmp -std=c++11 : 36.938 秒

【讨论】:

  • @LightnessRacesinOrbit 我用我得到的时间更新了答案
  • 你能推荐一个版本,在两种编译模式下都给出较低的数字吗?
  • @LightnessRacesinOrbit:对于这个特定的代码,完全摆脱对pow 的调用非常容易。 accum += power; power *= a[i] * a[i];
  • @BenVoigt:我为答案作者提供了扩展他们的答案以提供最佳方法的机会。这可能是最好的方法。但这是针对答案中的 OP,而不是针对 cme​​ts 中的我。
  • @oLen:非常感谢您的明确回答!我从没想过它会像 C++11 没有 pow(double, int) 版本那样“愚蠢”……
【解决方案2】:

除了@oLen 指出的函数重载选择问题之外,您还有错误共享,这会损害并行性。不要在每条语句中访问数组成员,它在内存中与其他线程中正在修改的元素直接相邻,这会导致缓存一致性算法的颠簸。而是将结果累积到一个临时的并且只写入一次结果数组:

for (int i = 0; i < testDataSize; ++i) {
    double accum = std::pow(a[i], 3) * std::exp(-a[i] * a[i]);
    accum += std::pow(a[i], 5) * std::exp(-a[i] * a[i]);
    accum += std::pow(a[i], 7) * std::exp(-a[i] * a[i]);
    accum += std::pow(a[i], 9) * std::exp(-a[i] * a[i]);
    accum += std::pow(a[i], 11) * std::exp(-a[i] * a[i]);
    accum += std::pow(a[i], 13) * std::exp(-a[i] * a[i]);
    accum += std::pow(a[i], 15) * std::exp(-a[i] * a[i]);
    accum += std::pow(a[i], 17) * std::exp(-a[i] * a[i]);
    accum += std::pow(a[i], 19) * std::exp(-a[i] * a[i]);
    accum += std::pow(a[i], 21) * std::exp(-a[i] * a[i]);
    b[i] = accum;
}

就此而言,只调用一次std::exp(-a[i] * a[i]) 并保存结果即使是单线程情况也应该有帮助,因为编译器很难证明这个公共子表达式可以被优化。最重要的是,在整个计算中考虑到这一点:

for (int i = 0; i < testDataSize; ++i) {
    double accum = std::pow(a[i], 3);
    accum += std::pow(a[i], 5);
    accum += std::pow(a[i], 7);
    accum += std::pow(a[i], 9);
    accum += std::pow(a[i], 11);
    accum += std::pow(a[i], 13);
    accum += std::pow(a[i], 15);
    accum += std::pow(a[i], 17);
    accum += std::pow(a[i], 19);
    accum += std::pow(a[i], 21);
    b[i] = accum * std::exp(-a[i] * a[i]);
}

【讨论】:

  • 我正要提到存储a[i]。很好的答案。
  • @erip: a[i] 只是被读取,而不是被写入,因此错误共享不会导致问题(所有内核都可以将缓存线处于状态S -- shared,read-仅)
  • 非常有用的建议。谢谢!
【解决方案3】:

除了@oLen 的出色回答之外,快速检查显示在以前的 libstdc++ 中pow(double, int) 只是__builtin_powi (double, int) 的一个转换,它通过乘法计算幂。发现通常不可能为pow(double, int)pow(double, double(int)) 产生相同的结果,因此在c++11 库中遵循标准实现被更改为使用pow(double, double),如果第二个参数是一个int 那么参与其中。 GCC 的文档也发生了变化,现在声明

— Built-in Function: double __builtin_powi (double, int)
    Returns the first argument raised to the power of the second. Unlike the pow function no guarantees about precision and rounding are made.

链接:https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-10
    • 2012-11-30
    • 2018-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多