openMp:调用动态数组的共享引用时性能严重损失
openMp: severe perfomance loss when calling shared references of dynamic arrays
我正在编写一个 cfd 模拟,并希望并行化我的 ~10^5 循环(点阵大小),这是成员函数的一部分。openMp 代码的实现很简单:我读取共享数组的条目,使用线程私有数量进行计算,最后再次写入共享数组。在每个数组中,我只访问循环号的数组元素,所以我不希望出现竞争条件,也看不到任何刷新的理由。测试代码的加速(并行部分),我发现除了一个 CPU 之外,所有 CPU 都只以 ~70% 的速度运行。有人知道如何改善这一点吗?
void class::funcPar(bool parallel){
#pragma omp parallel
{
int one, two, three;
double four, five;
#pragma omp for
for(int b=0; b<lenAr; b++){
one = A[b]+B[b];
C[b] = one;
one += D[b];
E[b] = one;
}
}
}
几点,然后测试代码,然后讨论:
- 如果每个项目都是
int
,则 10^5 并不多。启动多个线程所产生的开销可能大于好处。 - 使用 OMP 时,编译器优化可能会搞砸。
- 当每组内存处理少量操作时,循环可能会受到内存限制(即 CPU 花费时间等待请求的内存交付)
正如承诺的那样,这是代码:
#include <iostream>
#include <chrono>
#include <Eigen/Core>
Eigen::VectorXi A;
Eigen::VectorXi B;
Eigen::VectorXi D;
Eigen::VectorXi C;
Eigen::VectorXi E;
int size;
void regular()
{
//#pragma omp parallel
{
int one;
// #pragma omp for
for(int b=0; b<size; b++){
one = A[b]+B[b];
C[b] = one;
one += D[b];
E[b] = one;
}
}
}
void parallel()
{
#pragma omp parallel
{
int one;
#pragma omp for
for(int b=0; b<size; b++){
one = A[b]+B[b];
C[b] = one;
one += D[b];
E[b] = one;
}
}
}
void vectorized()
{
C = A+B;
E = C+D;
}
void both()
{
#pragma omp parallel
{
int tid = omp_get_thread_num();
int nthreads = omp_get_num_threads();
int vals = size / nthreads;
int startInd = tid * vals;
if(tid == nthreads - 1)
vals += size - nthreads * vals;
auto am = Eigen::Map<Eigen::VectorXi>(A.data() + startInd, vals);
auto bm = Eigen::Map<Eigen::VectorXi>(B.data() + startInd, vals);
auto cm = Eigen::Map<Eigen::VectorXi>(C.data() + startInd, vals);
auto dm = Eigen::Map<Eigen::VectorXi>(D.data() + startInd, vals);
auto em = Eigen::Map<Eigen::VectorXi>(E.data() + startInd, vals);
cm = am+bm;
em = cm+dm;
}
}
int main(int argc, char* argv[])
{
srand(time(NULL));
size = 100000;
int iterations = 10;
if(argc > 1)
size = atoi(argv[1]);
if(argc > 2)
iterations = atoi(argv[2]);
std::cout << "Size: " << size << "n";
A = Eigen::VectorXi::Random(size);
B = Eigen::VectorXi::Random(size);
D = Eigen::VectorXi::Random(size);
C = Eigen::VectorXi::Zero(size);
E = Eigen::VectorXi::Zero(size);
auto startReg = std::chrono::high_resolution_clock::now();
for(int i = 0; i < iterations; i++)
regular();
auto endReg = std::chrono::high_resolution_clock::now();
std::cerr << C.sum() - E.sum() << "n";
auto startPar = std::chrono::high_resolution_clock::now();
for(int i = 0; i < iterations; i++)
parallel();
auto endPar = std::chrono::high_resolution_clock::now();
std::cerr << C.sum() - E.sum() << "n";
auto startVec = std::chrono::high_resolution_clock::now();
for(int i = 0; i < iterations; i++)
vectorized();
auto endVec = std::chrono::high_resolution_clock::now();
std::cerr << C.sum() - E.sum() << "n";
auto startPVc = std::chrono::high_resolution_clock::now();
for(int i = 0; i < iterations; i++)
both();
auto endPVc = std::chrono::high_resolution_clock::now();
std::cerr << C.sum() - E.sum() << "n";
std::cout << "Timings:n";
std::cout << "Regular: " << std::chrono::duration_cast<std::chrono::microseconds>(endReg - startReg).count() / iterations << "n";
std::cout << "Parallel: " << std::chrono::duration_cast<std::chrono::microseconds>(endPar - startPar).count() / iterations << "n";
std::cout << "Vectorized: " << std::chrono::duration_cast<std::chrono::microseconds>(endVec - startVec).count() / iterations << "n";
std::cout << "Both : " << std::chrono::duration_cast<std::chrono::microseconds>(endPVc - startPVc).count() / iterations << "n";
return 0;
}
我使用 Eigen 作为向量库来帮助证明一个点 re:optimizations,我很快就会达到这个目标。代码以四种不同的优化模式编译:
g++ -fopenmp -std=c++11 -wall -pedantic -pthread -I C:\usr\include source.cpp -o a.exe
g++ -fopenmp -std=c++11 -wall -pedantic -pthread -O1 -I C:\usr\include source.cpp -o aO1.exe
g++ -fopenmp -std=c++11 -wall -pedantic -pthread -O2 -I C:\usr\include source.cpp -o aO2.exe
g++ -fopenmp -std=c++11 -wall -pedantic -pthread -O3 -I C:\usr\include source.cpp -o aO3.exe
在Windows下使用g++(x86_64-posix-sjlj,由 strawberryperl.com 项目构建)4.8.3。
讨论
我们将首先查看 10^5 与 10^6 个元素,在没有优化的情况下平均 100 次。
a 10^5(无优化):
Timings:
Regular: 9300
Parallel: 2620
Vectorized: 2170
Both : 910
a 10^6(无优化):
Timings:
Regular: 93535
Parallel: 27191
Vectorized: 21831
Both : 8600
矢量化 (SIMD) 在加速方面胜过 OMP。综合起来,我们会得到更好的时光。
移动到 -O1:
10^5:
Timings:
Regular: 780
Parallel: 300
Vectorized: 80
Both : 80
10^6:
Timings:
Regular: 7340
Parallel: 2220
Vectorized: 1830
Both : 1670
与没有优化相同,只是时间要好得多。
跳到 -O3:
10^5:
Timings:
Regular: 380
Parallel: 130
Vectorized: 80
Both : 70
10^6:
Timings:
Regular: 3080
Parallel: 1750
Vectorized: 1810
Both : 1680
对于 10^5,优化仍然胜过。但是,10^6 为 OMP 循环提供了比矢量化更快的时序。
在所有测试中,我们得到了大约 OMP 的 x2-x4 加速。
注意:我最初运行测试时,我有另一个使用所有内核的低优先级进程。出于某种原因,这主要影响并行测试,而不影响其他测试。确保正确计时。
结论
您的最小代码示例的行为与声明不同。更复杂的数据可能会出现内存访问模式等问题。添加足够的详细信息以准确重现您的问题 (MCVE) 以获得更好的帮助。
- 性能损失并行
- 在原始循环上使用boost::irange的性能损失
- 在C++代码中使用纯 C 库是否有性能下降/损失
- 可变的FlatBuffers,性能损失
- 通过Delphi访问Windows API是否会导致性能损失
- 在不损失C++或 Python 性能的情况下计算 pi
- 指针向量与值向量 大内存块与小内存块的性能损失
- 写入内存缓冲区时性能损失 (C++)
- 如果未在类声明中定义函数,则性能损失
- 库适配器性能损失
- 使用函子提供函数或运算符作为C++模板参数的性能损失
- 调用cuda内核时的性能损失
- MinGW g++从4.5.0更新到4.6.2后的性能损失
- 由于if语句,C++的性能损失巨大
- 在 Visual Studio 2010 中使用"auto"关键字的性能损失
- 在不损失性能的情况下提高可读性
- 只有2个元素的元组是否有任何性能损失?
- 在虚拟机上运行openMp算法造成的性能损失
- 在 Go 方法中按值传递"this"是否会对性能造成损失?
- 我是否应该使用"if"语句统一两个相似的内核,冒着性能损失的风险?