2012-03-13 10 views
2

C++ AMPを使用して並列計算用のアルゴリズム(Lattice Boltmann)を最適化しようとしています。また、メモリレイアウトを最適化するための提案を探して、構造から別のベクター(ブロックされたベクター)に1つのパラメータを削除すると約10%の増加となりました。並列計算用のメモリレイアウトを改善する

これ以上改善できるヒントがあれば誰でも気になるはずです。 以下は、タイムステップごとに実行される最も時間のかかる機能と、レイアウトに使用される構造です。

struct grid_cell { 
// int blocked; // Define if blocked 
    float n;  // North 
    float ne;  // North-East 
    float e;  // East 
    float se;  // South-East 
    float s; 
    float sw; 
    float w; 
    float nw; 
    float c;  // Center 
}; 

int collision(const struct st_parameters param, vector<struct grid_cell> &node, vector<struct grid_cell> &tmp_node, vector<int> &obstacle) { 
    int x,y; 
    int i = 0; 
    float c_sq = 1.0f/3.0f;  // Square of speed of sound 
    float w0 = 4.0f/9.0f;  // Weighting factors 
    float w1 = 1.0f/9.0f; 
    float w2 = 1.0f/36.0f; 

    int chunk = param.ny/20; 
    float total_density = 0; 

    float u_x,u_y;    // Avrage velocities in x and y direction 
    float u[9];     // Directional velocities 
    float d_equ[9];    // Equalibrium densities 
    float u_sq;     // Squared velocity 
    float local_density;  // Sum of densities in a particular node 

    for(y=0;y<param.ny;y++) { 
     for(x=0;x<param.nx;x++) { 
      i = y*param.nx + x; // Node index 
      // Dont consider blocked cells 
      if (obstacle[i] == 0) { 
       // Calculate local density 
       local_density = 0.0; 
       local_density += tmp_node[i].n; 
       local_density += tmp_node[i].e; 
       local_density += tmp_node[i].s; 
       local_density += tmp_node[i].w; 
       local_density += tmp_node[i].ne; 
       local_density += tmp_node[i].se; 
       local_density += tmp_node[i].sw; 
       local_density += tmp_node[i].nw; 
       local_density += tmp_node[i].c; 

       // Calculate x velocity component 
       u_x = (tmp_node[i].e + tmp_node[i].ne + tmp_node[i].se - 
         (tmp_node[i].w + tmp_node[i].nw + tmp_node[i].sw)) 
        /local_density; 
       // Calculate y velocity component 
       u_y = (tmp_node[i].n + tmp_node[i].ne + tmp_node[i].nw - 
         (tmp_node[i].s + tmp_node[i].sw + tmp_node[i].se)) 
        /local_density; 
       // Velocity squared 
       u_sq = u_x*u_x +u_y*u_y; 

       // Directional velocity components; 
       u[1] = u_x;  // East 
       u[2] =  u_y; // North 
       u[3] = -u_x;  // West 
       u[4] =  - u_y; // South 
       u[5] = u_x + u_y; // North-East 
       u[6] = -u_x + u_y; // North-West 
       u[7] = -u_x - u_y; // South-West 
       u[8] = u_x - u_y; // South-East 

       // Equalibrium densities 
       // Zero velocity density: weight w0 
       d_equ[0] = w0 * local_density * (1.0f - u_sq/(2.0f * c_sq)); 
       // Axis speeds: weight w1 
       d_equ[1] = w1 * local_density * (1.0f + u[1]/c_sq 
           + (u[1] * u[1])/(2.0f * c_sq * c_sq) 
           - u_sq/(2.0f * c_sq)); 
       d_equ[2] = w1 * local_density * (1.0f + u[2]/c_sq 
           + (u[2] * u[2])/(2.0f * c_sq * c_sq) 
           - u_sq/(2.0f * c_sq)); 
       d_equ[3] = w1 * local_density * (1.0f + u[3]/c_sq 
           + (u[3] * u[3])/(2.0f * c_sq * c_sq) 
           - u_sq/(2.0f * c_sq)); 
       d_equ[4] = w1 * local_density * (1.0f + u[4]/c_sq 
           + (u[4] * u[4])/(2.0f * c_sq * c_sq) 
           - u_sq/(2.0f * c_sq)); 
       // Diagonal speeds: weight w2 
       d_equ[5] = w2 * local_density * (1.0f + u[5]/c_sq 
           + (u[5] * u[5])/(2.0f * c_sq * c_sq) 
           - u_sq/(2.0f * c_sq)); 
       d_equ[6] = w2 * local_density * (1.0f + u[6]/c_sq 
           + (u[6] * u[6])/(2.0f * c_sq * c_sq) 
           - u_sq/(2.0f * c_sq)); 
       d_equ[7] = w2 * local_density * (1.0f + u[7]/c_sq 
           + (u[7] * u[7])/(2.0f * c_sq * c_sq) 
           - u_sq/(2.0f * c_sq)); 
       d_equ[8] = w2 * local_density * (1.0f + u[8]/c_sq 
           + (u[8] * u[8])/(2.0f * c_sq * c_sq) 
           - u_sq/(2.0f * c_sq)); 

       // Relaxation step 
       node[i].c = (tmp_node[i].c + param.omega * (d_equ[0] - tmp_node[i].c)); 
       node[i].e = (tmp_node[i].e + param.omega * (d_equ[1] - tmp_node[i].e)); 
       node[i].n = (tmp_node[i].n + param.omega * (d_equ[2] - tmp_node[i].n)); 
       node[i].w = (tmp_node[i].w + param.omega * (d_equ[3] - tmp_node[i].w)); 
       node[i].s = (tmp_node[i].s + param.omega * (d_equ[4] - tmp_node[i].s)); 
       node[i].ne = (tmp_node[i].ne + param.omega * (d_equ[5] - tmp_node[i].ne)); 
       node[i].nw = (tmp_node[i].nw + param.omega * (d_equ[6] - tmp_node[i].nw)); 
       node[i].sw = (tmp_node[i].sw + param.omega * (d_equ[7] - tmp_node[i].sw)); 
       node[i].se = (tmp_node[i].se + param.omega * (d_equ[8] - tmp_node[i].se)); 
      } 
     } 
    } 
    return 1; 
} 

答えて

6

現在のGPUはメモリのレイアウトによっては有名ですが、アプリケーションの詳細がなければ、ここで私はあなたが探検することをお勧めいくつかのものがあります:GPUは、「構造体の配列」を「配列の構造体」を好むよう

  1. ユニット・ストライドのアクセスが非常に重要です。フィールド "ブロック"をベクトル "障害物"に移動させると、 "grid_cell"のすべてのフィールドを別々のベクトルに変換することが有効です。これは、すべてのフィールドにアクセスしないループに対しても、CPUのメリットを示すはずです。

  2. 「障害物」(私はそうだと思いいる)、その後、独自のベクトルにそれを動かす非常にまばらである場合には特に値です。 CPUなどのGPUは、キャッシュラインや他の形式のメモリシステムから複数のワードをロードし、データの一部を必要としないときは帯域幅を無駄にします。多くのシステムメモリ帯域幅はボトルネックリソースであるため、帯域幅を削減する方法があれば役立ちます。

  3. これは、より投機的ですが、今は出力ベクトルのすべてを書いていること、メモリ・サブシステムは、単にいくつかのシステムでは

  4. 上書きされます「ノード」に読み値を回避されることが可能であり、オンチップメモリ​​はバンクに分割され、構造内の奇数個のフィールドを持つと、バンクの競合を解消できます。

  5. いくつかのシステムはまた、「ベクトル化」のロードとストアはとても再び構造から「ブロック」を除去しますより多くのベクトル化を可能にするかもしれません。構造体配列への移行は、この心配を緩和します。

C++ AMPに興味をお持ちいただきありがとうございます。

デビッド・キャラハン

http://blogs.msdn.com/b/nativeconcurrency/ C++ AMPチームのブログ

7

一般的には、異なるCPU上で使用されるデータは、(簡単に)共有されていないことを確認する必要があり、同じキャッシュライン上にない(:False Sharing is No Funを偽共有、ここでの例を参照します)。同じCPUによって使用されるデータは、キャッシュの恩恵を受けるためには互いに接近している必要があります。

1

いくつかの小さなジェネリックトップス:複数のプロセッサ間で共有されて

  • 任意のデータ構造は読み取り専用する必要があります。

  • 修正が必要なデータ構造は、プロセッサ固有のものであり、他のプロセッサが必要とするデータとメモリの局所性を共有しません。

  • コードが連続してスキャンされるようにメモリが配置されていることを確認します(巨大なステップを踏んだりジャンプしたりしないでください)。