gpt4 book ai didi

c++ - openMp:调用动态数组的共享引用时性能严重下降

转载 作者:行者123 更新时间:2023-11-28 06:12:55 24 4
gpt4 key购买 nike

我正在编写一个 cfd 模拟,并希望并行化我的 ~10^5 循环(晶格大小),它是成员函数的一部分。 openMp 代码的实现很简单:我读取共享数组的条目,使用线程私有(private)量进行计算,最后再次写入共享数组。在每个数组中,我只访问循环号的数组元素,所以我不希望出现竞争条件,也看不到任何刷新的理由。测试代码的加速(并行部分),我发现除了一个 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;
}
}

最佳答案

几点,然后测试代码,然后讨论:

  1. 如果每个项目都是 int,10^5 并不算多。启动多个线程所产生的开销可能大于 yield 。
  2. 使用 OMP 时,编译器优化可能会被打乱。
  3. 当处理每组内存的少量操作时,循环可以是内存绑定(bind)的(即 CPU 花费时间等待请求的内存被传送)

正如所 promise 的,这是代码:

#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作为一个 vector 库来帮助证明一个点 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 次。

10^5(没有优化):

Timings:
Regular: 9300
Parallel: 2620
Vectorized: 2170
Both : 910

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 循环提供了比矢量化更快的时序。

在所有测试中,我们获得了大约 x2-x4 的 OMP 加速。

注意:我最初是在有另一个低优先级进程使用所有内核时运行测试的。出于某种原因,这主要影响了并行测试,而不是其他。确保你的时间正确。

结论

您的最小代码示例与声明的不符。更复杂的数据可能会出现内存访问模式等问题。添加足够的详细信息以准确重现您的问题 (MCVE) 以获得更好的帮助。

关于c++ - openMp:调用动态数组的共享引用时性能严重下降,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30886056/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com