C ++ 11与OpenMP结合使用会产生较慢的可执行文件

时间:2015-12-22 15:37:09

标签: c++ c++11 openmp

我正在尝试学习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的回答。

3 个答案:

答案 0 :(得分:9)

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

修改 以下是我使用g ++版本4.8.4的时间:
原始版本:
-O3 -fopenmp: 10.678 s
-O3 -fopenmp -std = c ++ 11: 36.994 s
在整数后添加“.0”:
-O3 -fopenmp: 36.679 s
-O3 -fopenmp -std = c ++ 11: 36.938 s

答案 1 :(得分:6)

除了@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]);
}

答案 2 :(得分:3)

在@oLen的优秀答案中,快速检查显示在之前的libstdc ++ pow(double, int)只是__builtin_powi (double, int)的一个thunk,它通过乘法计算功率。结果发现,通常不可能为pow(double, int)pow(double, double(int))生成相同的结果,因此遵循c ++ 11库中的标准实现更改为使用pow(double, double)并且如果是第二个参数是一个涉及转换的int。海湾合作委员会的文件也发生了变化,现在说明了

— 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