0

非常に単純なコードが2つあります。私はそれらを次のように並列化しようとしています:

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コアのマシンでは、これら2つの小さなコードのスピードアップはわずか2倍です。これらの2つのコードは何かを足し合わせるだけなので、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

あなたのコードはメモリバウンドです - 各反復で膨大な量のデータをロードし、それに対して単純な (つまり高速な) 計算を行います。が 400 万の場合、、、 、のimgDim各要素が4 バイト (または配列など) と短い場合でも、それらの合計サイズは 80 MiB になり、最終レベルの CPU キャッシュには収まりません。デュアルソケットシステム。これらの配列が値を保持している場合 (変数に減らすという事実からもわかるように)、メモリ サイズが 2 倍になるため、事態はさらに悪化します。また、コードをベクトル化できる適切なコンパイラを使用する場合 (たとえば、デフォルトで実行する場合、GCC ではZZoU1U2U3floatintdoubledoubleicc-ftree-vectorize)、1 つのスレッドでも CPU ソケットのメモリ帯域幅を飽和させることができ、複数のスレッドで実行しても何のメリットもありません。

16 コア システムで見られる OpenMP の 2 倍のスピードアップは、このシステムに 2 つの CPU ソケットがあり、NUMA であるという事実から来ていると言えます。つまり、各ソケットに個別のメモリ コントローラーがあるため、16単一ソケットの 2 倍のメモリ帯域幅を使用するスレッド。これは、コードを 2 つのスレッドのみで実行し、それらを別の方法でバインドする場合に確認できます。つまり、同じソケットでコアごとに 1 つのスレッド、またはコアごとに別のソケットで 1 つのスレッドをバインドします。最初のケースではスピードアップはありませんが、2 番目のケースではスピードアップは約 2 倍になります。スレッドをコアにバインドすることは (まだ) 実装に依存します。Intel コンパイラを使用する場合は、GCC のGOMP_CPU_AFFINITYKMP_AFFINITYを調べることができます。

MPI の場合も同様です。スレッドではなくプロセスが使用されるようになりましたが、メモリ帯域幅の制限は残ります。さらに悪いことに、通信オーバーヘッドも追加され、問題のサイズが小さすぎると計算時間を超える可能性があります (比率はネットワーク相互接続に依存します - QDR InfiniBand ファブリックのような高速で潜在的な相互接続では低くなります) )。しかし、MPI を使用すると、より多くの CPU ソケットにアクセスできるため、より高い総メモリ帯域幅にアクセスできます。ソケットごとに 1 つの MPI プロセスでコードを起動して、システムから最高のパフォーマンスを引き出すことができます。その場合、プロセスバインディング (インテルの用語ではピニング) も重要です。

于 2013-02-08T11:48:45.630 に答える