29

大きな整数配列の各要素に対して実行される C++ で記述されたループがあります。ループ内で、整数の一部のビットをマスクしてから、最小値と最大値を見つけます。これらの操作に SSE 命令を使用すると、ビットごとの AND および if-else 条件を使用して記述された通常のループと比較して、はるかに高速に実行されると聞きました。私の質問は、これらの SSE の指示に従うべきですか? また、コードが別のプロセッサで実行されるとどうなりますか? それでも動作しますか、それともこれらの手順はプロセッサ固有のものですか?

4

15 に答える 15

25
  1. SSE 命令はプロセッサ固有です。ウィキペディアで、どのプロセッサがどの SSE バージョンをサポートしているかを調べることができます。
  2. SSE コードが高速になるかどうかは、多くの要因に依存します。最初の要因は、もちろん、問題がメモリ バウンドか CPU バウンドかです。メモリ バスがボトルネックである場合、SSE はあまり役に立ちません。整数計算を単純化してみてください。それによってコードが高速になる場合は、おそらく CPU バウンドであり、高速化できる可能性が高くなります。
  3. SIMD コードを記述することは C++ コードを記述することよりもはるかに難しく、結果のコードを変更するのははるかに難しいことに注意してください。C++ コードを常に最新の状態に保ちます。コメントとして使用したり、アセンブラー コードの正確性を確認したりできます。
  4. さまざまなプロセッサ用に最適化された一般的な低レベルの SIMD 操作を実装する、IPP のようなライブラリの使用について考えてみてください。
于 2009-02-25T16:09:32.587 に答える
16

SSE がその例である SIMD を使用すると、データの複数のチャンクに対して同じ操作を実行できます。したがって、SSE を整数演算の単純な代替として使用しても利点はありません。一度に複数のデータ項目に対して演算を実行できる場合にのみ利点が得られます。これには、メモリ内で連続するいくつかのデータ値をロードし、必要な処理を実行してから、配列内の次の値のセットに進むことが含まれます。

問題:

1 コード パスが処理されるデータに依存している場合、SIMD の実装は非常に難しくなります。例えば:

a = array [index];
a &= mask;
a >>= shift;
if (a < somevalue)
{
  a += 2;
  array [index] = a;
}
++index;

SIMDとして行うのは簡単ではありません:

a1 = array [index] a2 = array [index+1] a3 = array [index+2] a4 = array [index+3]
a1 &= mask         a2 &= mask           a3 &= mask           a4 &= mask
a1 >>= shift       a2 >>= shift         a3 >>= shift         a4 >>= shift
if (a1<somevalue)  if (a2<somevalue)    if (a3<somevalue)    if (a4<somevalue)
  // help! can't conditionally perform this on each column, all columns must do the same thing
index += 4

2 データが連続していない場合、データを SIMD 命令にロードするのは面倒です。

3 コードはプロセッサ固有です。SSE は IA32 (Intel/AMD) のみにあり、すべての IA32 CPU が SSE をサポートしているわけではありません。

アルゴリズムとデータを分析して、SSE が可能かどうかを確認する必要があり、それには SSE の仕組みを知る必要があります。Intel の Web サイトには多くのドキュメントがあります。

于 2009-02-25T16:24:23.673 に答える
11

この種の問題は、優れた低レベルのプロファイラーが不可欠であるという完璧な例です。(VTune のようなもの) ホットスポットがどこにあるかについて、より多くの情報に基づいたアイデアを提供できます。

私の推測では、あなたの説明から、あなたのホットスポットは、if/else を使用した最小/最大計算に起因する分岐予測の失敗である可能性が高いということです。したがって、SIMD 組み込み関数を使用すると、最小/最大命令を使用できるようになりますが、代わりに分岐のない最小/最大計算を使用してみる価値があるかもしれません。これにより、ほとんどの利益をより少ない痛みで達成できる可能性があります。

このようなもの:

inline int 
minimum(int a, int b)
{
  int mask = (a - b) >> 31;
  return ((a & mask) | (b & ~mask));
}
于 2009-02-26T16:19:09.153 に答える
6

SSE命令を使用する場合、明らかにこれらをサポートするプロセッサに制限されます。つまり、x86は、Pentium 2かそこらにまでさかのぼります(いつ導入されたか正確には思い出せませんが、かなり前のことです)

私が思い出す限り、整数演算を提供するSSE2は、やや最近のものです(Pentium 3?最初のAMD Athlonプロセッサはそれらをサポートしていませんでしたが)

いずれの場合も、これらの手順を使用するには2つのオプションがあります。コードのブロック全体をアセンブリで記述します(おそらく悪い考えです。コンパイラがコードを最適化することは事実上不可能であり、人間が効率的なアセンブラを記述することは非常に困難です)。

または、コンパイラで使用可能な組み込み関数を使用します(メモリが機能する場合、通常はxmmintrin.hで定義されます)

ただし、パフォーマンスが向上しない場合があります。SSEコードは、処理するデータに追加の要件を課します。主に覚えておくべきことは、データは128ビット境界で整列する必要があるということです。また、同じレジスタにロードされた値の間に依存関係がほとんどないか、まったくないはずです(128ビットSSEレジスタは4つのintを保持できます。最初と2番目のレジスタを足し合わせるのは最適ではありません。ただし、4つのintすべてを対応する4つのintに追加します。別のレジスタが高速になります)

すべての低レベルのSSEのいじりをラップするライブラリを使用したくなるかもしれませんが、それは潜在的なパフォーマンス上の利点を台無しにする可能性もあります。

SSEの整数演算サポートがどれほど優れているかはわかりません。そのため、パフォーマンスを制限する要因になる可能性もあります。SSEは、主に浮動小数点演算の高速化を目的としています。

于 2009-02-25T16:15:15.333 に答える
4

Microsoft Visual C ++を使用する場合は、次の内容をお読みください。

http://www.codeproject.com/KB/recipes/sseintro.aspx

于 2009-02-25T16:19:03.000 に答える
3

SSEで、あなたが説明したものと似ていますが、バイト配列にいくつかの画像処理コードを実装しました。Intelコンパイラに関しても、正確なアルゴリズムによっては4倍以上になりますが、Cコードと比較して大幅に高速化されます。ただし、すでに述べたように、次の欠点があります。

  • 移植性。コードはすべてのIntelのようなCPUで実行され、AMDでも実行されますが、他のCPUでは実行されません。ターゲットハードウェアを制御するため、これは問題ではありません。コンパイラを切り替えたり、64ビットOSに切り替えたりすることも問題になる可能性があります。

  • あなたは急な学習曲線を持っていますが、原則を理解した後、新しいアルゴリズムを書くことはそれほど難しいことではないことがわかりました。

  • 保守性。ほとんどのCまたはC++プログラマーは、アセンブリ/SSEの知識がありません。

パフォーマンスの向上が本当に必要で、Intel IPPのようなライブラリで問題の関数を見つけることができず、移植性の問題に対処できる場合にのみ、それを実行することをお勧めします。

于 2009-02-25T16:16:19.047 に答える
3

私の経験から、SSE は単純な C バージョンのコード (インライン asm も組み込み関数も使用しない) よりも大幅な (4 倍以上) 高速化をもたらすことがわかりますが、手動で最適化されたアセンブラーは、コンパイラーが生成できる場合、コンパイラーで生成されたアセンブリーを打ち負かすことができます。プログラマーが意図したことを理解することはできません (私を信じてください、コンパイラーはすべての可能なコードの組み合わせをカバーしているわけではなく、決してカバーすることはありません)。ああ、コンパイラは、可能な限り最速で実行するデータを常にレイアウトできるわけではありません。ただし、Intel コンパイラよりも高速化するには、多くの経験が必要です (可能であれば)。

于 2010-07-10T22:11:28.070 に答える
2

SSE 命令はもともと Intel チップだけにありましたが、最近 (Athlon 以来?) AMD もサポートしているため、SSE 命令セットに対してコードを作成する場合は、ほとんどの x86 proc に移植できるはずです。

そうは言っても、x86 のアセンブラーに慣れていない限り、SSE コーディングを学ぶのに時間をかける価値はないかもしれません。より簡単なオプションは、コンパイラーのドキュメントをチェックして、コンパイラーが SSE コードを自動生成できるようにするオプションがあるかどうかを確認することです。あなたのために。一部のコンパイラは、この方法でループを非常にうまくベクトル化します。(Intel コンパイラがこれをうまく処理すると聞いても、おそらく驚かないでしょう :)

于 2009-02-25T16:12:09.197 に答える
2

コンパイラが何をしているかを理解するのに役立つコードを記述します。GCC は、次のような SSE コードを理解して最適化します。

typedef union Vector4f
{
        // Easy constructor, defaulted to black/0 vector
    Vector4f(float a = 0, float b = 0, float c = 0, float d = 1.0f):
        X(a), Y(b), Z(c), W(d) { }

        // Cast operator, for []
    inline operator float* ()
    { 
        return (float*)this;
    }

        // Const ast operator, for const []
    inline operator const float* () const
    { 
        return (const float*)this;
    }

    // ---------------------------------------- //

    inline Vector4f operator += (const Vector4f &v)
    {
        for(int i=0; i<4; ++i)
            (*this)[i] += v[i];

        return *this;
    }

    inline Vector4f operator += (float t)
    {
        for(int i=0; i<4; ++i)
            (*this)[i] += t;

        return *this;
    }

        // Vertex / Vector 
        // Lower case xyzw components
    struct {
        float x, y, z;
        float w;
    };

        // Upper case XYZW components
    struct {
        float X, Y, Z;
        float W;
    };
};

ビルド パラメータに -msse -msse2 を指定することを忘れないでください。

于 2009-02-27T08:44:16.027 に答える
1

異なる CPU で使用可能な異なる SSE バージョンについて以前に述べたことに簡単に追加します。これは、CPUID 命令によって返されるそれぞれの機能フラグを調べることで確認できます (詳細については、Intel のドキュメントなどを参照してください)。

于 2009-02-26T11:49:12.610 に答える
1

C/C++ のインライン アセンブラをご覧ください。DDJの記事はこちらです。あなたのプログラムが互換性のあるプラットフォームで動作することを 100% 確信していない限り、多くの人がここに示した推奨事項に従う必要があります。

于 2009-02-26T12:01:51.643 に答える
1

以前のポスターに同意します。メリットは非常に大きくなる可能性がありますが、それを得るには多くの作業が必要になる場合があります。これらの手順に関するインテルのドキュメントは、4K ページを超えています。Ocali Inc. から無料で提供されている EasySSE (組み込み関数の C++ ラッパー ライブラリ + サンプル) をチェックしてみてください。

この EasySSE との関係は明らかだと思います。

于 2011-11-29T20:07:09.597 に答える
1

SIMD 組み込み関数 (SSE2 など) を使用すると、この種の処理を高速化できますが、正しく使用するには専門知識が必要です。これらは、アライメントとパイプラインのレイテンシに非常に敏感です。不注意に使用すると、パフォーマンスが低下する可能性があります。単純にキャッシュ プリフェッチを使用して、すべての int を操作するのに間に合うように L1 に配置するだけで、はるかに簡単かつ迅速に高速化できます。

関数が 1 秒あたり 100,000,000 整数よりも優れたスループットを必要としない限り、SIMD はおそらく問題に値しません。

于 2009-02-26T08:43:04.367 に答える
1

SSE が一部のプロセッサに固有であることは事実ですが (SSE は比較的安全である可能性があり、私の経験では SSE2 ははるかに安全です)、実行時に CPU を検出し、ターゲット CPU に応じてコードを動的にロードできます。

于 2009-02-25T16:31:16.980 に答える
0

組み立てにかなり熟練していない限り、これを自分で行うことはお勧めしません。Skizz が指摘しているように、SSE を使用するには、おそらくデータを慎重に再編成する必要があり、その利点はせいぜい疑わしいものです。

非常に小さなループを記述し、データを非常に緊密に整理し、これを行うコンパイラーに依存する方がおそらくはるかに良いでしょう。インテル C コンパイラーと GCC (4.1 以降) はどちらもコードを自動ベクトル化でき、おそらくあなたよりも優れた仕事をするでしょう。(CXXFLAGS に -ftree-vectorize を追加するだけです。)

編集:私が言及すべきもう1つのことは、いくつかのコンパイラがアセンブリ組み込みをサポートしていることです。おそらく、IMOは、asm()または__asm {}構文よりも使いやすいでしょう。

于 2009-02-25T18:01:38.027 に答える