-
06-09-2019 - |
質問
C++ で書かれたループがあり、大きな整数配列の各要素に対して実行されます。ループ内で、整数の一部のビットをマスクし、最小値と最大値を見つけます。これらの操作に SSE 命令を使用すると、ビット単位の AND および if-else 条件を使用して作成された通常のループと比較して、はるかに高速に実行されると聞きました。私の質問は、これらの SSE 手順に従ったほうがよいでしょうか?また、コードが別のプロセッサで実行された場合はどうなりますか?それでも動作しますか? それともこれらの命令はプロセッサー固有のものですか?
解決
- SSE 命令はプロセッサー固有です。どのプロセッサがどの SSE バージョンをサポートしているかについては、ウィキペディアで確認できます。
- SSE コードが高速かどうかは、多くの要因によって決まります。1 つ目は、問題がメモリに依存しているのか、CPU に依存しているのかということです。メモリ バスがボトルネックの場合、SSE はあまり役に立ちません。整数の計算を簡素化してみてください。これによりコードが高速化する場合は、おそらく CPU 依存であり、高速化できる可能性が高くなります。
- SIMD コードを記述するのは C++ コードを記述するよりもはるかに難しく、結果として得られるコードを変更するのははるかに難しいことに注意してください。C++ コードを常に最新の状態に保ってください。これをコメントとして使用し、アセンブラー コードの正確さを確認することができます。
- さまざまなプロセッサ向けに最適化された一般的な低レベル SIMD 操作を実装する、IPP のようなライブラリの使用を検討してください。
他のヒント
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命令にデータをロードcontigousない場合が煩雑である
3コードは、プロセッサ固有のものです。 SSEは、IA32(インテル/ AMD)の上だけではなく、すべてのIA32のCPUをサポートSSEます。
あなたはそれがSSE'dことができるかどうかを確認するために、アルゴリズムとデータを分析する必要があり、それはSSEがどのように機能するかを知ることが必要です。インテルのウェブサイト上の文書がたくさんあります。
この種の問題は、良好な低レベルのプロファイラが不可欠であるところの完璧な例です。 (インテル®VTuneのようなもの)それはあなたのホットスポットがどこにあるのずっとより多くの情報にアイデアを与えることができます。
あなたが記述するものから私の推測では、あなたのホットスポットは、おそらく場合/他の使用最小/最大計算結果、分岐予測の失敗になるということです。したがって、SIMD組み込み関数を使用すると、しかし、それはだけではなく、無店舗最小/最大caluculationを使用しようとしている価値があるかもしれません、あなたは最小/最大命令を使用できるようにする必要があります。これは、痛みが少なくと利益のほとんどを達成することがあります。
このような何かます:
inline int
minimum(int a, int b)
{
int mask = (a - b) >> 31;
return ((a & mask) | (b & ~mask));
}
、あなたは明らかにこれらをサポートするプロセッサに制限されています。 それは2かそこらは、ペンティアムにさかのぼる、x86のを意味します(それらが導入されたとき、正確に思い出すことができないが、それは長い時間前です)。
(最初のAMD Athlonプロセッサは、それらをサポートしていませんでしたが?ペンティアム3)私の知る限り思い出すことができるように、整数演算を提供しています一つであり、幾分最近のことである、SSE2、
いずれにせよ、あなたは、これらの命令を使用するための2つのオプションがあります。どちらか(それはそれは事実上不可能コンパイラがコードを最適化できるようになります。おそらく悪い考え、そしてそれは、効率的なアセンブラを書くために、人間のために非常に困難です)アセンブリ内のコードのブロック全体を書きます。
また、あなたのコンパイラで利用できる組み込み関数を使用します(メモリが提供する場合、それらは通常xmmintrin.hで定義されている)。
しかし、再び、パフォーマンスが向上しない場合があります。 SSEコードは、それが処理するデータの追加要件を提起します。主に、心に留めておくべき1は、データは128ビットの境界に配置されなければならないということです。またそこ第一緒が最適でない第二のものを追加する。(128ビットSSEレジスタが4つのint値を保持することができ、いくつかのまたは同一のレジスタにロードされる値の間に依存性である。しかしに対応する4つのint型にすべての4つのintを追加すべきです別のレジスタが速くなります)
すべての低レベルSSEのあいをラップライブラリを使用したくてもよいが、それはまた、任意の潜在的なパフォーマンス上の利点を台無しにするかもしれません。
私は、SSEの整数演算のサポートはどのように良いかわからないので、それはまた、性能を制限することができ要因となり得ます。 SSEは、主に浮動小数点演算を高速化を対象としています。
は、MicrosoftのVisual C ++のを使用する場合は、あなたがこれを読んでくださいます:
あなたが説明したものと似ていますが、SSEのバイト配列にいくつかの画像処理コードを実装しました。C コードと比較した場合、Intel コンパイラに関してさえ、正確なアルゴリズムに応じて 4 倍以上の大幅な速度向上が見られます。ただし、すでに述べたように、次の欠点があります。
携帯性。このコードは、Intel 系のすべての CPU で実行され、AMD でも実行されますが、他の CPU では実行されません。私たちはターゲットのハードウェアを制御しているので、それは問題ではありません。コンパイラの切り替え、さらには 64 ビット OS への切り替えでも問題が発生する可能性があります。
学習曲線は急勾配ですが、原則を理解すれば、新しいアルゴリズムを作成するのはそれほど難しくないことがわかりました。
保守性。ほとんどの C または C++ プログラマーは、アセンブリ/SSE の知識がありません。
私からのアドバイスは、本当にパフォーマンスの向上が必要で、インテル IPP のようなライブラリーで問題を解決する関数が見つからず、移植性の問題を許容できる場合にのみ、この方法を採用することです。
私は(、何の組み込み関数を使用しない無インラインASM)SSEは、コードの通常のCバージョンを超える(最大4倍と)巨大なスピードアップをもたらすというのが私のexperinceから伝えることができますが、手に最適化されたアセンブラ場合、コンパイラが生成するアセンブリを打つことができますコンパイラは、(コンパイラは、すべての可能なコードの組み合わせをカバーしていないと、彼らは決して、私を信じて)プログラマが意図したかを把握することはできません。 ああと、コンパイラは毎回、それが最速の可能な速度で動作し、データをレイアウトすることはできません。 しかし、あなたは、Intelコンパイラ(可能であれば)以上の高速化のために多くののexperinceを必要とします。
SSE命令は(アスロン以降?)最近、ちょうどIntelチップに元々あったが、AMDは、同様にそれらをサポートしていますので、あなたは、SSE命令セットに対してコードを実行する場合は、ほとんどのx86 procsのに移植する必要があります。
言われていること、あなたが既にのx86の上でアセンブラに精通していない限りSSEコーディングを学ぶためにあなたの時間の価値ではないかもしれない - 簡単にオプションは、コンパイラのドキュメントをチェックすることとするコンパイラを許可するオプションがある場合は表示される場合がありますあなたのためのSSEコードを自動生成。いくつかのコンパイラは非常によく、このようにループをベクトル化します。 (おそらく、インテル®コンパイラーは、この良い仕事をすると聞いて驚いていないよ:)
コンパイラは、あなたが何をしているかを理解するのに役立つコードを記述します。 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を持っていることを忘れないでください!
、実行時にCPUを検出し、ターゲットCPUに応じて動的にコードをロードすることができます。
SIMD組み込み関数は、この種のものをスピードアップするが、正しく使用するために専門知識を取ることができます。彼らは、アライメントやパイプラインの待ち時間に非常に敏感です。不注意な使用は、それはそれらなしであったであろうよりもパフォーマンスがさらに悪化することができます。あなたは、単にすべてのint型は、あなたがそれらを操作するための時間にL1であることを確認するために、キャッシュのプリフェッチを使用してから、ずっと簡単に、より即時のスピードアップを取得します。
あなたの関数は毎秒よりも良好億の整数のスループットを必要としない限り、、SIMDは、おそらくあなたのためのトラブル価値はありません。
ちょうど約異なるSSEのバージョンが異なるCPU上で利用可能になる前に言われているものを簡単に追加しますP>
のインラインアセンブラをC / C ++のために、ここにあるを見てくださいDDJの記事に。あなたは100%確実でない限り、あなたのプログラムでは、多くの人がここに与えた勧告に従うべきで互換性のあるプラットフォーム上で実行されます。
私は以前のポスターに同意します。メリットは非常に大きくなることができますが、それは多くの作業が必要になることがあり得るために。これらの命令にインテルのドキュメントでは、4Kページの上にあります。あなたはOcali社から無償をEasySSE(組み込み関数の上にC ++ラッパーライブラリ+例)をチェックアウトすることがあります。
私はこのEasySSEと私の所属が明確であると仮定します。
私はこれをやって自分をお勧めしません。 SSEは、おそらくよりも、あなたのデータの慎重な再編成が必要になります使用して Skizz のポイントなどアウト、および利点は、最高の状態でしばしば疑問である。
おそらく、あなたが非常に小さいループを書いて、非常にしっかり整理してデータを保持し、あなたのためだけにこれをやって、コンパイラに依存するのは非常に良いだろう。インテルCコンパイラとGCCの両方が(4.1以降)あなたのコードを自動ベクトル化することができ、そしておそらくより良い仕事をします。 (ちょうどあなたのCXXFLAGSに-ftree-ベクトル化を追加します。)
の の編集:私は言及すべきもう一つは、いくつかのコンパイラのサポートは、のアセンブリ組み込み関数の、おそらく、IMO、容易になるだろうこれは、ASM()または__asm {よりも使用するということです}構文