2012-02-10 79 views
2

我想知道如何在OpenMP中并行化一段代码,其中for循环的内部与其余部分是独立的。使用OpenMP会出现什么问题?

该项目基本上是处理粒子系统,但我认为这不应该与代码的并行化有关。它是一个缓存问题,for循环以一种方式划分线程,使得粒子不以有效的方式缓存在每个内核中?

编辑:正如下面的答案所述,我想知道为什么我没有得到加速。

#pragma omp parallel for 
for (unsigned i = 0; i < psize-n_dead; ++i) 
{ 
    s->particles[i].pos = s->particles[i].pos + dt * s->particles[i].vel; 
    s->particles[i].vel = (1 - dt*.1) * s->particles[i].vel + dt*s->force; 
    // printf("%d", omp_get_thread_num()); 

} 
+0

'psize-n_dead'有多大? – Mysticial 2012-02-10 20:03:01

+0

它随着时间而增长,但是在1000s左右。所以说4000是最简单的状态,并且可能最高会达到20万。 – user1202831 2012-02-10 20:07:54

回答

2

如果你问它是否是并行正确,它看起来不错。我没有看到任何可能破坏它的数据竞赛或循环依赖。

但我想你想知道为什么你没有得到任何加速并行。

既然您提到旅行计数,psize-n_dead将在4000的顺序。我会说,这实际上是相当小的循环中的工作量。

换句话说,你没有太多的全部工作值得并行化。所以线程开销可能会消耗任何你应该获得的加速。如果可能的话,你应该尝试在更高层次进行并行化。


编辑:您更新您的评论,包括高达200000

对于较大的值,很可能你会记忆在某种程度上约束。你的循环只是遍历所有完成很少工作的数据。所以使用更多的线程可能不会有太大的帮助(如果有的话)。

2

在这段代码中没有正确性问题,例如数据竞争。

假设要处理的粒子数量足够大以保证并行性,在此代码中我看不到OpenMP相关的性能问题。默认情况下,OpenMP会在所有线程中以相等的比例静态地分割循环迭代,所以任何高速缓存冲突可能只发生在这些部分的边界处,即仅在循环的几次迭代中。

与OpenMP无关(对于并行加速问题也是如此),可能通过从结构体数组切换到数组结构体来实现性能改进,因为这可能有助于编译器向量化代码(即使用一个目标处理器的SIMD指令):

#pragma omp parallel for 
for (unsigned i = 0; i < psize-n_dead; ++i) 
{ 
    s->particles.pos[i] = s->particles.pos[i] + dt * s->particles.vel[i]; 
    s->particles.vel[i] = (1 - dt*.1) * s->particles.vel[i] + dt*s->force; 
} 

这种重组假定大多数时间所有颗粒在像这样的一个循环处理。使用单个粒子需要加载更多的高速缓存行,但是如果您在循环中全部处理它们,则加载的高速缓存行的净数量几乎相同。

+0

+1用于暗示数组结构。由于我只是专注于平行度部分,所以我从未想过这一点。 – Mysticial 2012-02-10 20:27:42

1

你有多确定你没有加速?

尝试它两种方式 - 数组的结构和数组,用gcc -O3编译(gcc 4。6)上的双四核Nehalem,我得到psize-n_dead = 200000,运行100次迭代获得更好的计时器精度:

阵列的结构(报告的时间以毫秒为单位)

$ for t in 1 2 4 8; do export OMP_NUM_THREADS=$t; time ./foo; done 
Took time 90.984000 
Took time 45.992000 
Took time 22.996000 
Took time 11.998000 

阵列结构的:

$ for t in 1 2 4 8; do export OMP_NUM_THREADS=$t; time ./foo; done 
Took time 58.989000 
Took time 28.995000 
Took time 14.997000 
Took time 8.999000 

不过,我因为操作是如此之短(亚毫秒),我没有看到任何的加速没有做,因为计时器精度100次迭代。此外,你必须有一台具有良好内存带宽的机器来获得这种行为;你只做〜3个FMA和读取的每两个数据的另一个乘法。

结构数组的代码如下所示。

#include <stdio.h> 
#include <stdlib.h> 
#include <sys/time.h> 

typedef struct particle_struct { 
    double pos; 
    double vel; 
} particle; 

typedef struct simulation_struct { 
    particle *particles; 
    double force; 
} simulation; 

void tick(struct timeval *t) { 
    gettimeofday(t, NULL); 
} 

/* returns time in seconds from now to time described by t */ 
double tock(struct timeval *t) { 
    struct timeval now; 
    gettimeofday(&now, NULL); 
    return (double)(now.tv_sec - t->tv_sec) + ((double)(now.tv_usec - t->tv_usec)/1000000.); 
} 


void update(simulation *s, unsigned psize, double dt) { 
#pragma omp parallel for 
    for (unsigned i = 0; i < psize; ++i) 
    { 
     s->particles[i].pos = s->particles[i].pos+ dt * s->particles[i].vel; 
     s->particles[i].vel = (1 - dt*.1) * s->particles[i].vel + dt*s->force; 
    } 
} 

void init(simulation *s, unsigned np) { 
    s->force = 1.; 
    s->particles = malloc(np*sizeof(particle)); 
    for (unsigned i=0; i<np; i++) { 
     s->particles[i].pos = 1.; 
     s->particles[i].vel = 1.; 
} 

int main(void) 
{ 
    const unsigned np=200000; 
    simulation s; 
    struct timeval clock; 

    init(&s, np); 
    tick(&clock); 
    for (int iter=0;iter< 100; iter++) 
     update(&s, np, 0.75); 
    double elapsed=tock(&clock)*1000.; 
    printf("Took time %lf\n", elapsed); 

    free(s.particles); 
} 
+0

我意识到主要的问题是,与整体时间成本相比,执行这组操作的成本太小,所以我提高了粒子的数量以增加整体影响并展示并行效率。 – user1202831 2012-02-17 04:26:45