我是OpenMP的初学者。我遇到过这样的问题。
我有一个长度为M
的掩码数组N
,其元素为either 0 or 1
。我希望提取满足i
的所有索引M[i]=1
,并将它们存储到新的数组T
中。
OpenMP可以加速这个问题吗?
我试过以下代码。但它没有表现效果。
int count = 0;
#pragma omp parallel for
for(int i = 0; i < N; ++i) {
if(M[i] == hashtag) {
int pos = 0;
#pragma omp critical (c1)
pos = count++;
T[pos] = i;
}
答案 0 :(得分:4)
我不是100%肯定这会好得多,但你可以尝试以下方法:
int count = 0;
#pragma omp parallel for
for(int i = 0; i < N; ++i) {
if(M[i]) {
#pragma omp atomic
T[count++] = i;
}
}
如果数组非常稀疏,则线程可以通过大量的零来压缩,而无需等待其他零。但是您一次只能更新一个索引。问题实际上是不同的线程正在写入同一个内存块(T
),这意味着你将遇到缓存问题:每次一个线程写入T
时,所有的缓存其他核心是“脏”的 - 所以当他们试图修改它时,在幕后会发生很多改组。所有这一切对你来说都是透明的(你不需要编写代码来处理它)但它会显着降低速度 - 我怀疑这是你真正的瓶颈。如果您的矩阵足够大,可以让您值得一试,那么您可以尝试执行以下操作:
它可能更快(因为不同的线程不写入相同的内存) - 但由于循环内的语句很少,我怀疑它不会。
编辑我创建了一个完整的测试程序,并发现了两件事。首先,atomic
指令在omp
的所有版本中都不起作用,您可能必须使用T[count++] += i;
来进行编译(这是可以的,因为T可以设置为最初全为零);更令人不安的是,如果你这样做,你将不会得到相同的答案两次(count
的最终值从一个传递到下一个传递);如果您使用critical
,则不会发生这种情况。
第二个观察结果是,当你增加线程数时,程序的速度确实会减慢,这证实了我怀疑共享内存的时间(处理了10M元素的时间:
threads elapsed
1 0.09s
2 0.73s
3 1.21s
4 1.62s
5 2.34s
通过更改稀疏矩阵M
的方式 - 当我创建M
作为随机数组,并测试M[i] < 0.01 * RAND_MAX
(0.1%密集矩阵)时,您可以看到这是真的。运行得比我使其密度高10%要快得多 - 显示critical
部分内的部分确实让我们放慢了速度。
在这种情况下,我认为没有办法在OMP中加快这项任务 - 最终将所有线程的输出合并到一个列表中的工作就是吃掉任何速度鉴于内循环内部的变化很小,你可能已经拥有的优势。因此,我建议您尽可能高效地重写循环,而不是使用多个线程,例如:
for( i = 0; i < N; i++) {
T[count] = i;
count += M[i];
}
在我的快速基准测试中,这比OMP解决方案更快 - 与threads = 1
解决方案相当。再次 - 这是因为这里访问内存的方式。请注意,我避免使用if
语句 - 这使代码尽可能快。相反,我利用M[i]
始终为零或一的事实。在循环结束时,您必须丢弃元素T[count]
,因为它将无效......“好元素”是T[0] ... T[count-1]
。在我的机器上,这个循环在~0.02秒内处理了一个带有10M元素的数组M
。应该足够用于大多数目的吗?
答案 1 :(得分:2)
基于Floris的快速功能,我试着看看我是否能找到一种方法来使用OpenMP找到更快的解决方案。我提出了两个函数foo_v2
和foo_v3
,它们对于较大的数组更快,foo_v2
更快地独立于密度,foo_v3
对于稀疏数组更快。函数foo_v2
实际上创建了一个宽度为N*nthreads
的2D数组,以及一个包含每个线程计数的数组countsa
。用代码可以更好地解释这一点。以下代码将遍历写入T的所有元素。
for(int ithread=0; ithread<nthreads; ithread++) {
for(int i=0; i<counta[ithread]; i++) {
T[ithread*N/nthread + i]
}
}
函数foo_v3
根据请求创建一维数组。在所有情况下,N
必须非常大才能克服OpenMP开销。下面的代码默认为256MB,密度为M
约10%。在我的4核心Sandy Bridge系统上,OpenMP功能的速度提高了2倍。如果您将密度设置为50%foo_v2
,则速度提高约2倍,但foo_v3
不再更快。
#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
int foo_v1(int *M, int *T, const int N) {
int count = 0;
for(int i = 0; i<N; i++) {
T[count] = i;
count += M[i];
}
return count;
}
int foo_v2(int *M, int *T, int *&counta, const int N) {
int nthreads;
#pragma omp parallel
{
nthreads = omp_get_num_threads();
const int ithread = omp_get_thread_num();
#pragma omp single
counta = new int[nthreads];
int count_private = 0;
#pragma omp for
for(int i = 0; i<N; i++) {
T[ithread*N/nthreads + count_private] = i;
count_private += M[i];
}
counta[ithread] = count_private;
}
return nthreads;
}
int foo_v3(int *M, int *T, const int N) {
int count = 0;
int *counta = 0;
#pragma omp parallel reduction(+:count)
{
const int nthreads = omp_get_num_threads();
const int ithread = omp_get_thread_num();
#pragma omp single
{
counta = new int[nthreads+1];
counta[0] = 0;
}
int *Tprivate = new int[N/nthreads];
int count_private = 0;
#pragma omp for nowait
for(int i = 0; i<N; i++) {
Tprivate[count_private] = i;
count_private += M[i];
}
counta[ithread+1] = count_private;
count += count_private;
#pragma omp barrier
int offset = 0;
for(int i=0; i<(ithread+1); i++) {
offset += counta[i];
}
for(int i=0; i<count_private; i++) {
T[offset + i] = Tprivate[i];
}
delete[] Tprivate;
}
delete[] counta;
return count;
}
void compare(const int *T1, const int *T2, const int N, const int count, const int *counta, const int nthreads) {
int diff = 0;
int n = 0;
for(int ithread=0; ithread<nthreads; ithread++) {
for(int i=0; i<counta[ithread]; i++) {
int i2 = N*ithread/nthreads+i;
//printf("%d %d\n", T1[n], T2[i2]);
int tmp = T1[n++] - T2[i2];
if(tmp<0) tmp*=-1;
diff += tmp;
}
}
printf("diff %d\n", diff);
}
void compare_v2(const int *T1, const int *T2, const int count) {
int diff = 0;
int n = 0;
for(int i=0; i<count; i++) {
int tmp = T1[i] - T2[i];
//if(tmp!=0) printf("%i %d %d\n", i, T1[i], T2[i]);
if(tmp<0) tmp*=-1;
diff += tmp;
}
printf("diff %d\n", diff);
}
int main() {
const int N = 1 << 26;
printf("%f MB\n", 4.0*N/1024/1024);
int *M = new int[N];
int *T1 = new int[N];
int *T2 = new int[N];
int *T3 = new int[N];
int *counta;
double dtime;
for(int i=0; i<N; i++) {
M[i] = ((rand()%10)==0);
}
//int repeat = 10000;
int repeat = 1;
int count1, count2;
int nthreads;
dtime = omp_get_wtime();
for(int i=0; i<repeat; i++) count1 = foo_v1(M, T1, N);
dtime = omp_get_wtime() - dtime;
printf("time v1 %f\n", dtime);
dtime = omp_get_wtime();
for(int i=0; i<repeat; i++) nthreads = foo_v2(M, T2, counta, N);
dtime = omp_get_wtime() - dtime;
printf("time v2 %f\n", dtime);
compare(T1, T2, N, count1, counta, nthreads);
dtime = omp_get_wtime();
for(int i=0; i<repeat; i++) count2 = foo_v3(M, T3, N);
dtime = omp_get_wtime() - dtime;
printf("time v2 %f\n", dtime);
printf("count1 %d, count2 %d\n", count1, count2);
compare_v2(T1, T3, count1);
}
答案 2 :(得分:1)
关键操作应该是atomic
而不是critical
;实际上,在您的情况下,您必须使用atomic capture
子句:
int pos, count = 0; // pos declared outside the loop
#pragma omp parallel for private(pos) // and privatized, count is implicitly
for(int i = 0; i < N; ++i) { // shared by all the threads
if(M[i]) {
#pragma omp atomic capture
pos = count++;
T[pos] = i;
}
}
查看this answer,了解使用OpenMP进行atomic
操作的所有可能性。