我正在尝试学习OpenMP,并希望使用OpenMP学习加速.为此,我写了以下小程序:
#include
#include
int main() {
static const unsigned int testDataSize = 1 << 28;
std::vector a (testDataSize), b (testDataSize);
for (int i = 0; i < testDataSize; ++i) {
a [i] = static_cast (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的回答.
原因是没有-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
除了函数重载选择问题@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]); }