22

「Restrict キーワードのわかりやすい説明」の最後に、次の興味深いアドバイスがあります。

GCC でスケジューリングが行われる順序により、常に式を単純化することをお勧めします。メモリ アクセスと計算を混在させないでください。コードは次のように書き直すことができます。

次に、これを本質的に変換する例があります

velocity_x[i] += acceleration_x[i] * time_step;

これに

const float ax  = acceleration_x[i];       // Then the same follows for y, z
const float vx  = velocity_x[i];           // etc for y, z
const float nvx = vx + ( ax * time_step ); // etc
velocity_x[i]   = nvx;                     // ...

本当に?この種の変換は、ラムダ引数などの最適化コンパイラが行う必要がある他のものと比較して些細なことだと思っていたでしょうstd::foreach

これは陳腐でばかげたアドバイスですか?それとも、GCC がこれを実行できない、または実行しない正当な理由がありますか? (私のクラスをvelocity += acceleration * time_step使って上記を書くのは心配です!Vector3f

4

2 に答える 2

15

編集:( 質問されている実際の質問から逸脱し、混乱を引き起こしているため、詳細を削除しています。OPは使用されていると想定しています。)restrictrestict

あなたの質問の変換は、最適化コンパイラにとって確かに些細なことですが、それはアクトンの論文が示唆していることではありません。

これが論文で行われた変換です:

このコード...

  for (size_t i=0;i<count*stride;i+=stride)
  {
    velocity_x[i] += acceleration_x[i] * time_step;
    velocity_y[i] += acceleration_y[i] * time_step;
    velocity_z[i] += acceleration_z[i] * time_step;
    position_x[i] += velocity_x[i]     * time_step;
    position_y[i] += velocity_y[i]     * time_step;
    position_z[i] += velocity_z[i]     * time_step;
  }

...このコードに変換されました:

  for (size_t i=0;i<count*stride;i+=stride)
  {
    const float ax  = acceleration_x[i];
    const float ay  = acceleration_y[i];
    const float az  = acceleration_z[i];
    const float vx  = velocity_x[i];
    const float vy  = velocity_y[i];
    const float vz  = velocity_z[i];
    const float px  = position_x[i];
    const float py  = position_y[i];
    const float pz  = position_z[i];

    const float nvx = vx + ( ax * time_step );
    const float nvy = vy + ( ay * time_step );
    const float nvz = vz + ( az * time_step );
    const float npx = px + ( vx * time_step );
    const float npy = py + ( vy * time_step );
    const float npz = pz + ( vz * time_step );

    velocity_x[i]   = nvx;
    velocity_y[i]   = nvy;
    velocity_z[i]   = nvz;
    position_x[i]   = npx;
    position_y[i]   = npy;
    position_z[i]   = npz;
  }

最適化とは何ですか?

最適化は、提案されているように、1つの式を3つの式に分離することではありません。

最適化とは、特定のデータを操作する命令の間に有用な命令を挿入することです。

velocity_x[i]に移動してvxからnvx戻ってデータを追跡する場合velocity_x[i]、CPUはこれらの各ステップ間で他の作業を実行しています。

なぜこれが最適化なのですか?

最近のCPUは通常、パイプラインアーキテクチャを備えています。

命令は段階的に実行されるため、CPUでは複数の命令を同時に処理できます。ただし、命令が完全に実行されていない別の命令の結果を必要とする場合、このパイプラインは停止します。停止した命令を実行できるようになるまで、それ以上の命令は実行されません。

最適化コンパイラがこれを自動的に実行しないのはなぜですか?

ある人はそうします。

GCCは、この最適化では比較的貧弱であるとして際立っています。

上記の両方のループをgcc4.7(x86-64アーキテクチャ、-O3での最適化)を使用して分解しました。同様のアセンブリが作成されましたが、命令の順序が異なり、最初のバージョンでは、いくつかの命令の範囲内で単一のフロートがロード、変更、および保存される重要なストールが生成されました。

ここでgccの命令スケジューリングについて少し読むか、Webでgcc命令スケジューリングを検索して、この問題に関する多くの不満のある記事を確認してください。

于 2013-02-26T16:03:28.173 に答える
1

私の意見では、古い/ばかげたアドバイスです。つまり、詳細レベルは、コンパイラ、コンパイラのバージョン、プロセッサ、プロセッサのバージョンなどに固有のものです。読みやすさに固執し、コンパイラにその仕事を任せます。特定のターゲットで 1 つか 2 つのクロック サイクルが発生する可能性があることを心配している場合は、そのターゲット用の #def アセンブリを作成し、他のターゲットと参照用に上位レベルのコードをそこに残します。

于 2013-02-26T16:40:45.427 に答える