整个程序已缩减为一个简单的测试:
const int loops = 1e10;
int j[4] = { 1, 2, 3, 4 };
time_t time = std::time(nullptr);
for (int i = 0; i < loops; i++) j[i % 4] += 2;
std::cout << std::time(nullptr) - time << std::endl;
int k[4] = { 1, 2, 3, 4 };
omp_set_num_threads(4);
time = std::time(nullptr);
#pragma omp parallel for
for (int i = 0; i < loops; i++) k[omp_get_thread_num()] += 2;
std::cout << std::time(nullptr) - time << std::endl;在第一种情况下,运行循环大约需要3秒,在第二种情况下,结果是不一致的,可能是4-9秒。这两个循环运行得更快,并且启用了一些优化(比如支持速度和整个程序优化),但是第二个循环仍然显着地慢。我尝试在循环结束时添加屏障,并显式地将数组指定为shared,但这没有帮助。当我设法使并行循环运行得更快时,唯一的情况是使循环变为空。可能出了什么问题?
Windows 10 x64,CPU Intel Core i5 10300 H (4核)
发布于 2022-04-07 07:12:32
正如在各种评论中已经指出的,问题的症结是虚假共享。事实上,你的例子就是一个人可以实验的典型例子。但是,您的代码中也有很多问题,例如:
loops变量以及所有j和k表中看到溢出;i%4公式,并添加了一个schedule( static, 1)子句。这不是一种正确的方法,但是它只是在没有使用正确的reduction子句的情况下得到预期的结果。然后,我重写了您的示例,并添加了我认为更好的解决错误共享问题的方法:使用reduction子句。
#include <iostream>
#include <omp.h>
int main() {
const long long loops = 1e10;
long long j[4] = { 1, 2, 3, 4 };
double time = omp_get_wtime();
for ( long long i = 0; i < loops; i++ ) {
j[i % 4] += 2;
}
std::cout << "sequential: " << omp_get_wtime() - time << std::endl;
time = omp_get_wtime();
long long k[4] = { 1, 2, 3, 4 };
#pragma omp parallel for num_threads( 4 ) schedule( static, 1 )
for ( long long i = 0; i < loops; i++ ) {
k[i%4] += 2;
}
std::cout << "false sharing: " << omp_get_wtime() - time << std::endl;
time = omp_get_wtime();
long long l[4] = { 1, 2, 3, 4 };
#pragma omp parallel for num_threads( 4 ) reduction( +: l[0:4] )
for ( long long i = 0; i < loops; i++ ) {
l[i%4] += 2;
}
std::cout << "reduction: " << omp_get_wtime() - time << std::endl;
bool a = j[0]==k[0] && j[1]==k[1] && j[2]==k[2] && j[3]==k[3];
bool b = j[0]==l[0] && j[1]==l[1] && j[2]==l[2] && j[3]==l[3];
std::cout << "sanity check: " << a << " " << b << std::endl;
return 0;
}在我的笔记本上编译和运行而不进行优化:
$ g++ -O0 -fopenmp false.cc
$ ./a.out
sequential: 15.5384
false sharing: 47.1417
reduction: 4.7565
sanity check: 1 1这说明了reduction条款所带来的改进。现在,从编译器中启用优化提供了一个更缓和的画面:
$ g++ -O3 -fopenmp false.cc
$ ./a.out
sequential: 4.8414
false sharing: 4.10714
reduction: 2.10953
sanity check: 1 1如果说有什么区别的话,这表明编译器现在很擅长于避免大多数错误的共享。实际上,对于初始(错误的) k[omp_get_thread_num()],使用和不使用reduction子句都没有时间差,这表明编译器能够避免这个问题。
https://stackoverflow.com/questions/71767545
复制相似问题