如何解决没有线程本地副本的 OpenMP 减少数组
我希望 OpenMP 将一个 大 数组缩减为一个较小的动态数组。例如,其中
large[] = {1,1,2,3,3};
// OpenMP reduce length-3 sublists of large,to produce:
small[] = {3,6,9};
我的要求与 this question 类似,但我还有一些重要的附加限制:
- 必须支持 OpenMP 3.1(所以我不能像 this answer 那样使用 OpenMP 4.5 的数组缩减)
- 不能有
small
的线程私有副本(如 this answer),因为small
可以和large
一样大,因此线程私有副本可能导致堆栈溢出 -
large
数组必须按顺序迭代,以获得良好的缓存(因为它可能非常大)
其他一些细节:
-
large
的相加元素不一定相邻 - 这种减少发生在一个函数中。
small
(输出)数组由用户预先分配,并通过引用传递。理想情况下,该函数不应分配small
的新本地副本
-
small
和large
都具有2
次幂的长度(例如2
、4
、8
、{{1} } ...)。16
的每个元素都是从相同数量的small
元素(2 的另一个幂)减少的。
为了清楚起见,以下是串行伪代码示例:
large
这是一个示例实现,由于它使用了 OpenMP 4.5 的数组缩减,因此不合格。此外,它不合需要地使用了 void myfunc(double* small,double* large,int lLen,// other args) {
for (int i=0; i<lLen; i++) {
int sInd = // determined by other args
small[sInd] += large[i];
}
}
的本地副本(根据使用 small
的要求)。
reduction
我希望我可以在 OpenMP 3.1 中实现同样的事情,甚至可能通过自己管理数组元素减少来避免 void myfunc(double* small,int sLen,// other args) {
double smallLocal[sLen];
int i,sInd;
#pragma omp parallel private (i) for
for (i=0; i<sLen; s++)
smallLocal[i] = 0;
#pragma omp parallel private (i,sInd) reduction (+:smallLocal) for
for (i=0; i<largeLen; i++) {
sInd = // determined by other args
smallLocal[sInd] += large[i];
}
#pragma omp parallel private (i) for
for (i=0; i<sLen; s++)
small[i] = smallLocal[i];
}
的本地副本。我该怎么做?
解决方法
您可以使用 OpenMPs atomic update
构造,它已经存在于 OpenMP 3.1 中:
void myfunc(double* small,double* large,int lLen,// other args) {
#pragma omp parallel for
for (int i=0; i<lLen; i++) {
int sInd = // determined by other args
#pragma omp atomic update
small[sInd] += large[i];
}
}
这应该比使用锁更快。
,这里有一个使用锁的不太令人满意的解决方案,它需要一个与 locks
一样大的本地 small
数组(可能需要 malloc'd)。
void myfunc(double* small,int sLen,// other args) {
int i,sInd;
// clear small
#pragma omp parallel private (i) for
for (i=0; i<sLen; s++)
small[i] = 0;
// initialise locks
#ifdef _OPENMP
omp_lock_t locks[sLen];
#pragma omp parallel private (i) for
for (i=0; i<sLen; s++)
omp_init_lock(&locks[i]);
#endif
// main loop
#pragma omp parallel private (i,sInd) for
for (i=0; i<largeLen; i++) {
sInd = // determined by other args
// update small with locks
#ifdef _OPENMP
omp_set_lock(&locks[sInd]);
small[sInd] += large[i];
omp_unset_lock(&locks[sInd]);
#else
small[sInd] += large[i];
#endif
}
// destroy locks
#ifdef _OPENMP
#pragma omp parallel private (i) for
for (i=0; i<sLen; s++)
omp_destroy_lock(&locks[i]);
#endif
}
正如预期的那样,这比使用 OpenMP 4.5 的数组缩减要慢:
- 对于
sLen = 8
、lLen = 1048576
,使用锁的速度 26 倍 - 对于
sLen = 32768
、lLen = 1048576
,使用锁的速度 1.5 倍
需要说明的是,OpenMP 4.5 的数组缩减只兼容本地栈数组,不兼容动态内存。这将缩减数组的大小限制为 ~1 MiB,或 2^17 双倍 (lLen = 131072
)。相比之下,这个锁解决方案可以处理任何大小,因为 locks
可以被 malloc'd。
你可以做这种在科学代码中很常见的事情:
#pragma omp parallel for
for(int i = 0; i < N/3; i++)
{
for (int j = 0; j < 3; j++)
{
small[i] += large[3*i + j];
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。