1

シミュレーション プログラムでサイクルを構成する 2 フェーズ プロセスがあります。多かれ少なかれ、私は次のものを持っています:

struct Coordinates
{
   double * x, * y, * z;
   uint * kind, count;
   double GetDist(const uint p1, const uint p2);
};

struct Polynomial
{
   double * A, * B;
   uint n1, n2;
   uint Flatten(const uint i, const uint j);
   double CalcResult(double distSq, uint kind1, uint kind2)
   {
      uint ij = Flatten(kind1, kind2);
      double base = B * distSq;
      return A[ij]*(pow(base,n2)-pow(base,n1));
   }
};

私の質問は、私のコードを次のように書くかどうかです

struct Model
{
   Coordinates c;
   Polynomial f;
   double DoTest()
   {
      double result = 0;
      uint count = 0;
      std::vector<double> distSq;
      for (uint i=0; i<c.count; i++)
      {
         for (uint j=i; j<c.count; j++)
         {
            result = c.GetDist(i,j);
            distSq.push_back(result);
         }
      }
      result = 0;
      for (uint i=0; i<c.count; i++)
      {
         for (uint j=i; j<c.count; j++)
         {
            result += f.CalcResult(distSq[count], i, j);
            count++;
         }
      }
      return result;
   }
   double DoTest2()
   {
      double result = 0;
      for (uint i=0; i<c.count; i++)
         for (uint j=i; j<c.count; j++)
            result += f.CalcResult(c.GetDist(i,j), i, j);
      return result;
   }
}

Test単一のデータセットでの反復操作を考慮して、x86 チップで並列処理 (ベクトル化された数学や改善されたメモリアクセスなど) を自動的に有効にしますか?

それ以外の場合Testはガベージ アプローチです。追加のストレージ ( std::vector<double> distSq;) を使用し、コードの読み取りに関してははるかに長くなります。論理的にはほぼ同じですが、GetDist f_A(関数 A) とCalcResult f_B(関数 B) を呼び出すと、Test は次のようになります。

f_A f_A f_A ... f_A    f_B f_B .... f_B

より短い/より少ないメモリ集約型の機能はどこにありますか

f_A f_B f_A f_B .... f_A f_B

-O#生成されたベクトル化された数学演算などが原因で、コンパイルされた C コードでいわゆる「固有の並列処理」についての話を聞いたことがありTestます。単一のデータセットに対する反復操作?

(それ以外の場合Test2は、使用するメモリが少ないため、唯一の合理的なアプローチです。)

また、c-style xy、およびz配列をstd::vector<double>代替のものに置き換えると、何らかの方法で計算またはメモリアクセスを高速化できる可能性がありますか?

Test「自分自身をベンチマークする」と答えないでください...コンパイラと「固有の並列処理」に基づいて、理論的な観点からベンチマークを介してアプローチをテストする価値があるかどうかについて、より良い理解を得ようとする理由です。

4

2 に答える 2

1

クラシック SIMD コンパイラーの最適化

コンパイラーによる SIMD 命令での最適化が容易であることが知られているコードの簡単な例を以下に示します。

for (int i = 0; i < N; ++i) 
     C[i] = A[i] + B[i];

VC++ を使用した SIMD 最適化の例

あなたの場合

あなたの最初のループc.GetDistは、すべての反復が互いに独立しているように見えますが、GetDist実際に何をするかによって、結果をベクトルにプッシュバックすることと組み合わせて、コンパイラが SIMD 命令を生成するよりも難しくなる可能性があると思います組み込み配列に 2 つのベクトルを追加するだけの場合。ただし、私はコンパイラの専門家ではないので、間違っている可能性があります。コンパイラによっても異なる場合があります。

確実に知る最善の方法は、コードをコンパイルし、逆アセンブリを見て、コンパイラによって生成された命令の種類を確認することです。たとえば、IA-32 または 64 ビット Intel を使用している場合は、MMX または XMM レジスタに作用する命令を探します。ベクトルを組み込み配列に置き換えて、違いがあるかどうかを確認することもできます。

インテル アセンブリ言語リファレンス

興味深い話

私は最近、Going Native 2013 カンファレンスで Jim Radigan による興味深い講演を見ました。彼は Microsoft C++ コンパイラ バックエンド チームで働いており、コードの最適化を専門としています。彼は、生成されたマシン コードで並列処理を実現するなど、いくつかの興味深いトピックに触れました。対談のリンクはこちら:

Jim Radigan がコンパイラーの最適化について語る

于 2013-09-24T00:24:44.293 に答える
1

メモリアクセスは、並列処理に関係なくあなたを殺します。.reserve(c.count*c.count())での再割り当てを防ぐために呼び出すところに小さな改善があります.push_backが、それだけでは十分ではありません。c.countが十分に大きい場合、これは L1 キャッシュと場合によっては L2 を無駄にします。

次の問題は、f_A関数がメモリ アクセスに依存していることです。最新のプロセッサは、その読み取りを発行してf_B、その間に以前のものを処理できます。データの依存関係はありません。それはTest2さらに効率的になります。

BYW、それは私だけですか、それとも CalcResult(i,j) と CalcResult(j,i) は非常に似ていますか? 計算を組み合わせることでメリットが得られる場合があります。

私は と を作りAますB double const*。結局のところ、あなたはそれらを通して書いているわけではありません。

うまくいくかもしれないのは#pragma omp for reduction(+, result).

于 2013-09-23T23:50:36.090 に答える