C++11 结合 OpenMP 提供更慢的可执行文件
C++11 in conjunction with OpenMP gives slower executable
我正在尝试学习 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 完全符合奥伦的回答。
原因是没有-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 s
在整数后添加“.0”:
-O3 -fopenmp:36.679 秒
-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]);
}
除了@oLen 的出色回答之外,快速检查表明在以前的 libstdc++ 中 pow(double, int)
只是 __builtin_powi (double, int)
的一个 thunk,后者通过乘法计算幂。人们发现,通常不可能为 pow(double, int)
和 pow(double, double(int))
产生相同的结果,因此遵循 c++11 库中的标准实现被更改为使用 pow(double, double)
并且如果第二个argument 是一个 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.
Link: https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html
我正在尝试学习 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 完全符合奥伦的回答。
原因是没有-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 s
在整数后添加“.0”:
-O3 -fopenmp:36.679 秒
-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]);
}
除了@oLen 的出色回答之外,快速检查表明在以前的 libstdc++ 中 pow(double, int)
只是 __builtin_powi (double, int)
的一个 thunk,后者通过乘法计算幂。人们发现,通常不可能为 pow(double, int)
和 pow(double, double(int))
产生相同的结果,因此遵循 c++11 库中的标准实现被更改为使用 pow(double, double)
并且如果第二个argument 是一个 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.
Link: https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html