我试图弄清楚如何在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());
}
答案 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);
}