OpenMP atomic 比阵列的 critical 慢得多
OpenMP atomic substantially slower than critical for array
我看到的 OpenMP omp atomic
的示例通常涉及更新标量,并且通常报告它比 omp critical
快。在我的应用程序中,我希望更新已分配数组的元素,不同线程将更新的元素之间有一些重叠,我发现 atomic 比 critical 慢得多。它是一个数组有什么区别吗?我是否正确使用它?
#include <stdlib.h>
#include <assert.h>
#include <omp.h>
#define N_EACH 10000000
#define N_OVERLAP 100000
#if !defined(OMP_CRITICAL) && !defined(OMP_ATOMIC)
#error Must define OMP_CRITICAL or OMP_ATOMIC
#endif
#if defined(OMP_CRITICAL) && defined(OMP_ATOMIC)
#error Must define only one of either OMP_CRITICAL or OMP_ATOMIC
#endif
int main(void) {
int const n = omp_get_max_threads() * N_EACH -
(omp_get_max_threads() - 1) * N_OVERLAP;
int *const a = (int *)calloc(n, sizeof(int));
#pragma omp parallel
{
int const thread_idx = omp_get_thread_num();
int i;
#ifdef OMP_CRITICAL
#pragma omp critical
#endif /* OMP_CRITICAL */
for (i = 0; i < N_EACH; i++) {
#ifdef OMP_ATOMIC
#pragma omp atomic update
#endif /* OMP_ATOMIC */
a[thread_idx * (N_EACH - N_OVERLAP) + i] += i;
}
}
/* Check result is correct */
#ifndef NDEBUG
{
int *const b = (int *)calloc(n, sizeof(int));
int thread_idx;
int i;
for (thread_idx = 0; thread_idx < omp_get_max_threads(); thread_idx++) {
for (i = 0; i < N_EACH; i++) {
b[thread_idx * (N_EACH - N_OVERLAP) + i] += i;
}
}
for (i = 0; i < n; i++) {
assert(a[i] == b[i]);
}
free(b);
}
#endif /* NDEBUG */
free(a);
}
请注意,在这个简化的示例中,我们可以提前确定哪些元素将重叠,因此在更新这些元素时仅应用 atomic
/critical
会更有效,但在我的实际应用中这是不可能的。
当我编译时使用:
gcc -O2 atomic_vs_critical.c -DOMP_CRITICAL -DNDEBUG -fopenmp -o critical
gcc -O2 atomic_vs_critical.c -DOMP_ATOMIC -DNDEBUG -fopenmp -o atomic
和 运行 与 time ./critical
我得到:
real 0m0.110s user 0m0.086s sys 0m0.058s
和 time ./atomic
,我得到:
real 0m0.205s user 0m0.742s sys 0m0.032s
所以它在关键部分使用了大约一半的挂钟时间(当我重复它时我得到了相同的时间)。
还有另一个 post claims critical is slower than atomic,但它使用标量,当我 运行 提供的代码时,原子结果实际上比关键结果稍快。
您的比较不公平:#pragma omp critical
放在 for
循环之前,因此编译器可以对您的循环进行矢量化,但 #pragma omp atomic update
在循环内部,这会阻止矢量化.矢量化的这种差异导致了惊人的运行时间。为了在循环内进行公平比较:
for (i = 0; i < N_EACH; i++) {
#ifdef OMP_CRITICAL
#pragma omp critical
#endif /* OMP_CRITICAL */
#ifdef OMP_ATOMIC
#pragma omp atomic update
#endif /* OMP_ATOMIC */
a[thread_idx * (N_EACH - N_OVERLAP) + i] += i;
}
由于这个矢量化问题,如果您只使用单线程,您实际程序的运行时间很可能会最短。
我看到的 OpenMP omp atomic
的示例通常涉及更新标量,并且通常报告它比 omp critical
快。在我的应用程序中,我希望更新已分配数组的元素,不同线程将更新的元素之间有一些重叠,我发现 atomic 比 critical 慢得多。它是一个数组有什么区别吗?我是否正确使用它?
#include <stdlib.h>
#include <assert.h>
#include <omp.h>
#define N_EACH 10000000
#define N_OVERLAP 100000
#if !defined(OMP_CRITICAL) && !defined(OMP_ATOMIC)
#error Must define OMP_CRITICAL or OMP_ATOMIC
#endif
#if defined(OMP_CRITICAL) && defined(OMP_ATOMIC)
#error Must define only one of either OMP_CRITICAL or OMP_ATOMIC
#endif
int main(void) {
int const n = omp_get_max_threads() * N_EACH -
(omp_get_max_threads() - 1) * N_OVERLAP;
int *const a = (int *)calloc(n, sizeof(int));
#pragma omp parallel
{
int const thread_idx = omp_get_thread_num();
int i;
#ifdef OMP_CRITICAL
#pragma omp critical
#endif /* OMP_CRITICAL */
for (i = 0; i < N_EACH; i++) {
#ifdef OMP_ATOMIC
#pragma omp atomic update
#endif /* OMP_ATOMIC */
a[thread_idx * (N_EACH - N_OVERLAP) + i] += i;
}
}
/* Check result is correct */
#ifndef NDEBUG
{
int *const b = (int *)calloc(n, sizeof(int));
int thread_idx;
int i;
for (thread_idx = 0; thread_idx < omp_get_max_threads(); thread_idx++) {
for (i = 0; i < N_EACH; i++) {
b[thread_idx * (N_EACH - N_OVERLAP) + i] += i;
}
}
for (i = 0; i < n; i++) {
assert(a[i] == b[i]);
}
free(b);
}
#endif /* NDEBUG */
free(a);
}
请注意,在这个简化的示例中,我们可以提前确定哪些元素将重叠,因此在更新这些元素时仅应用 atomic
/critical
会更有效,但在我的实际应用中这是不可能的。
当我编译时使用:
gcc -O2 atomic_vs_critical.c -DOMP_CRITICAL -DNDEBUG -fopenmp -o critical
gcc -O2 atomic_vs_critical.c -DOMP_ATOMIC -DNDEBUG -fopenmp -o atomic
和 运行 与 time ./critical
我得到:
real 0m0.110s user 0m0.086s sys 0m0.058s
和 time ./atomic
,我得到:
real 0m0.205s user 0m0.742s sys 0m0.032s
所以它在关键部分使用了大约一半的挂钟时间(当我重复它时我得到了相同的时间)。
还有另一个 post claims critical is slower than atomic,但它使用标量,当我 运行 提供的代码时,原子结果实际上比关键结果稍快。
您的比较不公平:#pragma omp critical
放在 for
循环之前,因此编译器可以对您的循环进行矢量化,但 #pragma omp atomic update
在循环内部,这会阻止矢量化.矢量化的这种差异导致了惊人的运行时间。为了在循环内进行公平比较:
for (i = 0; i < N_EACH; i++) {
#ifdef OMP_CRITICAL
#pragma omp critical
#endif /* OMP_CRITICAL */
#ifdef OMP_ATOMIC
#pragma omp atomic update
#endif /* OMP_ATOMIC */
a[thread_idx * (N_EACH - N_OVERLAP) + i] += i;
}
由于这个矢量化问题,如果您只使用单线程,您实际程序的运行时间很可能会最短。