使用和不使用多线程时,OpenMP For 循环产生不同的结果
OpenMP For-Loops yield different Results with and without Multithreading
我是多线程的新手,我在尝试并行处理一些 for 循环时发现了以下问题,我在其中操作 3D 数组。
当我 运行 代码仅使用单个线程时,我得到了我期望的 E_total
值。但是,当我将相同的代码与 多线程 和 OpenMP 一起使用时,我按以下方式设置 #pragma omp parallel for
// DO FIRST COMPUTATION STEP ON 3D ARRAY
#pragma omp parallel for
for (size_t ix = 0; ix < N; ix++) {
for (size_t iy = 0; iy < N; iy++) {
for (size_t iz = 0; iz < N; iz++) {
A1[ix][iy][iz] = ...;
}
}
}
// DO SECOND COMPUTATION --AFTER-- FIRST COMPUTATION
#pragma omp parallel for
for (size_t ix = 0; ix < N; ix++) {
for (size_t iy = 0; iy < N; iy++) {
for (size_t iz = 0; iz < N; iz++) {
A2[ix][iy][iz] = ...;
E_pot += something * A1[ix][iy][iz];
E_int += something * A2[ix][iy][iz];
}
}
}
E_total += (E_pot + E_int); // This result changes when 'omp parallel for' is used
我发现 E_total
的结果不同。
由于循环操作是附加的或特定于网格点的(在不同 ijk
之间独立),它们不应依赖于循环内的任何顺序。
是否有可能在前面所有的第一个循环操作完成之前开始第二个for循环?如果是这样,我怎样才能避免这种情况或者我需要注意哪些其他错误?
对不起,如果这是一个非常基本的问题,但我无法在网上找到相关问题。提前致谢!
此代码的问题在于不同线程之间存在竞争条件。 E_pot
和 E_int
变量在工作线程之间共享,因此线程会不时破坏彼此的值。
要解决此问题,请应用 reduction
子句(请参阅 OpenMP API 规范中的 Reduction Clauses and Directives):
// DO SECOND COMPUTATION --AFTER-- FIRST COMPUTATION
#pragma omp parallel for reduction(+:E_pot) reduction(+:E_int)
for (size_t ix = 0; ix < N; ix++) {
for (size_t iy = 0; iy < N; iy++) {
for (size_t iz = 0; iz < N; iz++) {
A2[ix][iy][iz] = ...;
E_pot += something * A1[ix][iy][iz];
E_int += something * A2[ix][iy][iz];
}
}
}
还有一些您可以查看的更改,看看它们是否有帮助:
根据 N
的值,可能值得在 parallel for
指令中添加一个 collapse(2)
子句(参见 Worksharing-Loop Construct)以将两个外部循环合并到然后运行 N*N
次迭代的单个循环。对于小 N
,线程可以更好地工作,因为更多的迭代可以分布在工作线程中。
如果您显式添加 schedule(static)
(当您什么都不说时,这是大多数 OpenMP 实现的默认设置,但在技术上不能保证),那么您可以将 nowait
添加到第一个循环.这样一来,在第一个并行循环结束时就没有隐式障碍,并且已经完成其工作块的线程可以继续进行第二个循环。 schedule(static)
是必需的,因为第一个和第二个循环都具有相同的并行化,然后该技巧就起作用了。注意:如果您为第一个循环添加了 collapse(2)
,那么第二个循环也需要具有 collapse(2)
,以便并行化相同。
我是多线程的新手,我在尝试并行处理一些 for 循环时发现了以下问题,我在其中操作 3D 数组。
当我 运行 代码仅使用单个线程时,我得到了我期望的 E_total
值。但是,当我将相同的代码与 多线程 和 OpenMP 一起使用时,我按以下方式设置 #pragma omp parallel for
// DO FIRST COMPUTATION STEP ON 3D ARRAY
#pragma omp parallel for
for (size_t ix = 0; ix < N; ix++) {
for (size_t iy = 0; iy < N; iy++) {
for (size_t iz = 0; iz < N; iz++) {
A1[ix][iy][iz] = ...;
}
}
}
// DO SECOND COMPUTATION --AFTER-- FIRST COMPUTATION
#pragma omp parallel for
for (size_t ix = 0; ix < N; ix++) {
for (size_t iy = 0; iy < N; iy++) {
for (size_t iz = 0; iz < N; iz++) {
A2[ix][iy][iz] = ...;
E_pot += something * A1[ix][iy][iz];
E_int += something * A2[ix][iy][iz];
}
}
}
E_total += (E_pot + E_int); // This result changes when 'omp parallel for' is used
我发现 E_total
的结果不同。
由于循环操作是附加的或特定于网格点的(在不同 ijk
之间独立),它们不应依赖于循环内的任何顺序。
是否有可能在前面所有的第一个循环操作完成之前开始第二个for循环?如果是这样,我怎样才能避免这种情况或者我需要注意哪些其他错误?
对不起,如果这是一个非常基本的问题,但我无法在网上找到相关问题。提前致谢!
此代码的问题在于不同线程之间存在竞争条件。 E_pot
和 E_int
变量在工作线程之间共享,因此线程会不时破坏彼此的值。
要解决此问题,请应用 reduction
子句(请参阅 OpenMP API 规范中的 Reduction Clauses and Directives):
// DO SECOND COMPUTATION --AFTER-- FIRST COMPUTATION
#pragma omp parallel for reduction(+:E_pot) reduction(+:E_int)
for (size_t ix = 0; ix < N; ix++) {
for (size_t iy = 0; iy < N; iy++) {
for (size_t iz = 0; iz < N; iz++) {
A2[ix][iy][iz] = ...;
E_pot += something * A1[ix][iy][iz];
E_int += something * A2[ix][iy][iz];
}
}
}
还有一些您可以查看的更改,看看它们是否有帮助:
根据 N
的值,可能值得在 parallel for
指令中添加一个 collapse(2)
子句(参见 Worksharing-Loop Construct)以将两个外部循环合并到然后运行 N*N
次迭代的单个循环。对于小 N
,线程可以更好地工作,因为更多的迭代可以分布在工作线程中。
如果您显式添加 schedule(static)
(当您什么都不说时,这是大多数 OpenMP 实现的默认设置,但在技术上不能保证),那么您可以将 nowait
添加到第一个循环.这样一来,在第一个并行循环结束时就没有隐式障碍,并且已经完成其工作块的线程可以继续进行第二个循环。 schedule(static)
是必需的,因为第一个和第二个循环都具有相同的并行化,然后该技巧就起作用了。注意:如果您为第一个循环添加了 collapse(2)
,那么第二个循环也需要具有 collapse(2)
,以便并行化相同。