使用OpenMP可能会出现什么问题?

时间:2012-02-10 20:01:18

标签: c parallel-processing openmp

我试图弄清楚如何在OpenMP中并行化一段代码,其中for循环的内部独立于其余部分。

基本上该项目正在处理粒子系统,但我认为这不应该与代码的并行化相关。它是一个缓存问题,其中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());

}

3 个答案:

答案 0 :(得分:2)

如果您正确地询问它是否 并行化 ,它看起来很好。我没有看到任何可能破坏它的数据争用或循环依赖。

但我认为你想知道为什么你没有获得并行性的任何加速。

由于您提到行程计数,psize-n_dead将在4000的顺序。我认为考虑到循环中的工作量,这实际上非常小。

换句话说,您没有太多的工作值得并行化。因此,线程开销可能正在消耗你应该获得的任何加速。如果可能,您应该尝试更高级别的并行化。


编辑:您更新了评论,最多包含200000。

对于较大的值,您可能会以某种方式受到内存限制。你的循环只是迭代所有数据做很少的工作。因此,使用更多线程可能无济于事(如果有的话)。

答案 1 :(得分: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;
}

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

答案 2 :(得分: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

然而,我因为操作太短(sub-ms)而没有因为计时器的准确性而没有进行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);
}