8

3D 計算の最適化を開発しており、現在は次のようになっています。

  • plain標準 C 言語ライブラリを使用した " " バージョン、
  • プリプロセッサをSSE使用してコンパイルする最適化されたバージョン#define USE_SSE
  • AVXプリプロセッサを使用してコンパイルする最適化バージョン#define USE_AVX

異なる実行可能ファイルをコンパイルせずに 3 つのバージョンを切り替えることは可能inlineですか? このようなスイッチをソフトウェアに入れることで、パフォーマンスも考慮したいと思います。

4

3 に答える 3

8

これにはいくつかの解決策があります。

1 つは C++ に基づいており、複数のクラスを作成します。通常、インターフェイス クラスを実装し、ファクトリ関数を使用して正しいクラスのオブジェクトを提供します。

例えば

class Matrix
{
   virtual void Multiply(Matrix &result, Matrix& a, Matrix &b) = 0;
   ... 
};

class MatrixPlain : public Matrix
{
   void Multiply(Matrix &result, Matrix& a, Matrix &b);

};


void MatrixPlain::Multiply(...)
{
   ... implementation goes here...
}

class MatrixSSE: public Matrix
{
   void Multiply(Matrix &result, Matrix& a, Matrix &b);
}

void MatrixSSE::Multiply(...)
{
   ... implementation goes here...
}

... same thing for AVX... 

Matrix* factory()
{
    switch(type_of_math)
    {
       case PlainMath: 
          return new MatrixPlain;

       case SSEMath:
          return new MatrixSSE;

       case AVXMath:
          return new MatrixAVX;

       default:
          cerr << "Error, unknown type of math..." << endl;
          return NULL;
    }
}

または、上で提案したように、共通のインターフェイスを持つ共有ライブラリを使用して、適切なライブラリを動的にロードすることもできます。

もちろん、Matrix 基本クラスを「プレーンな」クラスとして実装する場合は、段階的な改良を行い、実際に見つけた部分のみを実装することが有益であり、基本クラスに依存して、パフォーマンスがそれほど重要ではない関数を実装することができます。

編集:あなたはインラインについて話していますが、そうであれば、間違ったレベルの機能を見ていると思います。かなりの量のデータに対して何かを行うかなり大きな関数が必要です。そうしないと、データを適切な形式に準備し、いくつかの計算命令を実行してから、データをメモリに戻すことにすべての労力が費やされてしまいます。

また、データの保管方法についても検討します。X、Y、Z、W を含む配列のセットを格納していますか?それとも、多数の X、多数の Y、多数の Z、多数の W を別々の配列に格納していますか?[3D 計算を行っていると仮定して]? 計算の仕方によっては、どちらかの方法を実行することで最大の利益が得られる場合があります。

私はかなりの量の SSE と 3DNow を実行しました! 数年前の最適化であり、「トリック」は多くの場合、データをどのように保存するかに関するものであり、適切な種類のデータの「バンドル」を一度に簡単に取得できます。データを間違った方法で保存すると、「データのスウィズリング」(ある保存方法から別の保存方法にデータを移動すること) に多くの時間を浪費することになります。

于 2013-01-18T22:02:41.457 に答える
6

1 つの方法は、同じインターフェイスに準拠する 3 つのライブラリを実装することです。動的ライブラリを使用すると、ライブラリ ファイルを交換するだけで、実行可能ファイルは見つかったものを使用します。たとえば、Windows では、次の 3 つの DLL をコンパイルできます。

  • PlainImpl.dll
  • SSEImpl.dll
  • AVXImpl.dll

そして、に対して実行可能リンクを作成しImpl.dllます。3 つの特定の DLL の 1 つを と同じディレクトリに置き.exe、名前を に変更するとImpl.dll、そのバージョンが使用されます。基本的に、UNIX ライクな OS にも同じ原則が当てはまります。

次のステップは、ライブラリをプログラムでロードすることです。これはおそらく最も柔軟ですが、OS 固有であり、さらに作業が必要です (ライブラリを開く、関数ポインタを取得するなど)。

編集:もちろん、他の回答で説明されているように、パラメーター/構成ファイルの設定などに応じて、関数を3回実装し、実行時に1つを選択することもできます。

于 2013-01-18T21:50:04.267 に答える
0

もちろん可能です。

それを行う最善の方法は、完全なジョブを実行する関数を用意し、実行時にそれらの中から選択することです。これは機能しますが、最適ではありません。

typedef enum
{
    calc_type_invalid = 0,
    calc_type_plain,
    calc_type_sse,
    calc_type_avx,
    calc_type_max // not a valid value
} calc_type;

void do_my_calculation(float const *input, float *output, size_t len, calc_type ct)
{
    float f;
    size_t i;

    for (i = 0; i < len; ++i)
    {
        switch (ct)
        {
            case calc_type_plain:
                // plain calculation here
                break;
            case calc_type_sse:
                // SSE calculation here
                break;
            case calc_type_avx:
                // AVX calculation here
                break;
            default:
                fprintf(stderr, "internal error, unexpected calc_type %d", ct);
                exit(1);
                break
        }
    }
}

ループを通過するたびに、コードはswitchステートメントを実行しますが、これは単なるオーバーヘッドです。本当に賢いコンパイラなら理論的には修正できますが、自分で修正する方がよいでしょう。

代わりに、plain 用、SSE 用、AVX 用の 3 つの個別の関数を記述します。次に、どちらを実行するかを実行時に決定します。

ボーナス ポイントについては、「デバッグ」ビルドで、SSE とプレーンの両方で計算を行い、結果が信頼できるほど近いことを主張します。速度のためではなく、正確さのために、単純なバージョンを書きます。次に、その結​​果を使用して、巧妙に最適化されたバージョンが正しい答えを得ることを確認します。

伝説的なジョン・カーマックは後者のアプローチを推奨しています。彼はそれを「並列実装」と呼んでいます。それについての彼のエッセイを読んでください。

そのため、プレーン バージョンを最初に作成することをお勧めします。次に、戻って SSE または AVX アクセラレーションを使用してアプリケーションの一部を書き直し、アクセラレーションされたバージョンが正しい答えを返すことを確認します。(また、通常のバージョンには高速化されたバージョンにはないバグがある場合があります。2 つのバージョンを用意してそれらを比較すると、どちらのバージョンでもバグが明らかになります。)

于 2013-01-18T21:53:18.947 に答える