0

我有两个非常简单的代码。我试图将它们并行如下:

double sk = 0, ed = 0;
        #pragma omp parallel shared(Z,Zo,U1,U2,U3) private(i) reduction(+: sk, ed)
        {
            #pragma omp for
            for (i=0;i<imgDim;i++)
            {
                sk += (Z[i]-Zo[i])*(Z[i]-Zo[i]);
                ed += U1[i]*U1[i] + U2[i]*U2[i] + U3[i]*U3[i];
            }
        }

///////////////////////////////////////// ///////////////////////////////

double rk = 0, epri = 0, ex = 0, ez = 0;
            #pragma omp parallel shared(X,Z) private(i) reduction(+: rk, ex,ez)
            {
                #pragma omp for
                for(i = 0; i<imgDim; i++)
                {
                    rk += (X[0][i]-Z[i])*(X[0][i]-Z[i]) + (X[1][i]-Z[i])*(X[1][i]-Z[i]) + (X[2][i]-Z[i])*(X[2][i]-Z[i]);    
                    ex += X[0][i]*X[0][i] + X[1][i]*X[1][i] + X[2][i]*X[2][i];
                    ez += Z[i]*Z[i];
                }
            }

Z、Zo、U1、U2、U3、X 都是大矩阵。imgDim 是 400 万。速度没有想象中那么快。在一台16核的机器上,这两条小代码的速度只有两倍。我不明白为什么 OMP 会出现这种行为,因为这两个代码只是加起来而已。这应该是OMP擅长的。

更奇怪的行为是,当我尝试使用 MPI 并行这些代码时,MPI 会减慢速度,如下所示:

int startval = imgDim*pid/np;
int endval = imgDim*(pid+1)/np-1;
int ierr;
double p_sum_sk = 0;
double p_sum_ed = 0;

for (i=startval;i<=endval;i++)
{
    sk += (Z[i]-Zo[i])*(Z[i]-Zo[i]);
    ed += U1[i]*U1[i] + U2[i]*U2[i] + U3[i]*U3[i];
}

MPI_Reduce(&sk, &p_sum_sk, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
MPI_Reduce(&ed, &p_sum_ed, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
MPI_Bcast(&sk, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
MPI_Bcast(&ed, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);

///////////////////////////////////////// //////////////////////////////

int startval = imgDim*pid/np;
int endval = imgDim*(pid+1)/np-1;
double p_sum_rk = 0.;
double p_sum_ex = 0.;
double p_sum_ez = 0.;


for(i = startval; i<=endval; i++)
{
    rk = rk + (X[0][i]-Z[i])*(X[0][i]-Z[i]) + (X[1][i]-Z[i])*(X[1][i]-Z[i]) + (X[2][i]-Z[i])*(X[2][i]-Z[i]);
    ex += X[0][i]*X[0][i] + X[1][i]*X[1][i] + X[2][i]*X[2][i];
    ez += Z[i]*Z[i];
}

MPI_Reduce(&rk,&p_sum_rk,1,MPI_DOUBLE,MPI_SUM,0,MPI_COMM_WORLD);
MPI_Reduce(&ex,&p_sum_ex,1,MPI_DOUBLE,MPI_SUM,0,MPI_COMM_WORLD);
MPI_Reduce(&ez,&p_sum_ez,1,MPI_DOUBLE,MPI_SUM,0,MPI_COMM_WORLD);
MPI_Bcast(&rk,1,MPI_INT,0,MPI_COMM_WORLD);
MPI_Bcast(&rk,1,MPI_INT,0,MPI_COMM_WORLD);
MPI_Bcast(&epri,1,MPI_INT,0,MPI_COMM_WORLD);

np 是处理器的数量,pid 是当前处理器的 id。在我使用 32 甚至 64 处理器后,它并没有显示出任何加速。它甚至比顺序代码还要慢。我不懂为什么。这些代码只是在添加东西。OMP 和 MPI 应该擅长它。谁能帮我一把?

4

1 回答 1

1

您的代码受内存限制 - 您在每次迭代中加载大量数据并对其进行简单(即快速)计算。如果imgDim是 400 万,那么即使Z, Zo, U1, U2,的每个元素U3短至 4 个字节(例如,它们是floatint数组),它们的总大小也将是 80 MiB,即使给定,这也不适合最后一级 CPU 缓存双插座系统。double如果这些数组保存值(正如您减少为变量的事实所暗示的那样),情况会更糟double,因为它会使内存大小增加两倍。此外,如果您使用一个体面的编译器,它能够对代码进行矢量化(例如icc,默认情况下会这样做,GCC 需要-ftree-vectorize),即使是单个线程也能够使 CPU 插槽的内存带宽饱和,然后使用多个线程运行将不会带来任何好处。

我想说的是,您在 16 核系统上观察到的 2 倍 OpenMP 加速来自这样一个事实,即该系统有两个 CPU 插槽并且是 NUMA,即它在每个插槽上都有一个单独的内存控制器,因此在运行 16您使用两倍于单个套接字的内存带宽的线程。如果您仅使用两个线程运行代码,但以不同的方式绑定它们,则可以验证这一点:同一套接字上的每个内核一个线程或每个内核一个线程但在不同的套接字上。在第一种情况下应该没有加速,而在第二种情况下,加速应该是大约 2 倍。将线程绑定到内核是(还)依赖于实现的——如果你碰巧使用英特尔编译器,你可以看看GOMP_CPU_AFFINITY for GCC 和KMP_AFFINITY 。

这同样适用于 MPI 案例。现在您有进程而不是线程,但内存带宽限制仍然存在。情况更糟,因为现在还增加了通信开销,如果问题规模太小,它可能会超过计算时间(该比率取决于网络互连 - 随着 QDR InfiniBand 结构等更快和更少潜在的互连,它会更低)。但是使用 MPI,您可以访问更多 CPU 插槽,从而获得更高的总内存带宽。您可以使用每个套接字一个 MPI 进程来启动您的代码,以从您的系统中获得最佳性能。在这种情况下,进程绑定(或英特尔术语中的固定)也很重要。

于 2013-02-08T11:48:45.630 回答