ベクトル化でコンパイラを支援しますか? -邪魔しないほうがいい。

これは、Intel Software Networkの英語版に関する最近の投稿の無料翻訳です。 だからそれら vikky13よりもVictoria Zhislinaが好きな人 すでにこの投稿を見た人は、オリジナルにない最初と最後の段落をすぐに読むことができます。



-みなさん、こんにちは。ロシア語からC ++プログラムコードへの翻訳者が必要です。 まあ、つまり、私はタスクを書いており、翻訳者はそのソリューションをC ++で実装しています。 どこで見つけることができますか? Cにない場合、他の言語にある可能性がありますか?



-はい、開発部長と呼ばれます。 あなたがロシア語でタスクを書くなら、あなたは部下にそれを与えます、そして、それで、コードは準備ができています! Cでも、Delphiでも、Javaでも。 動作確認しました!



彼らはこれは冗談ではなく、プログラミングフォーラムでの本当の質問だと言います。 彼らはまた、人は機械よりもはるかに賢い、つまり彼が彼女を助けることができると言う-彼女の心を共有する。 しかし、これが間違いなく実行する価値がない場合が多くあります。 結果は予想とは逆になります。



以下は、有名なオープンソースOpenCVライブラリの良い例です
cvtScale_( const Mat&amp; srcmat, Mat&amp; dstmat, double _scale, double _shift ) { Op op; typedef typename Op::type1 WT; typedef typename Op::rtype DT; Size size = getContinuousSize( srcmat, dstmat, srcmat.channels() ); WT scale = saturate_cast&lt;WT&gt;(_scale), shift = saturate_cast&lt;WT&gt;(_shift); for( int y = 0; y < size.height; y++ ) { const T* src = (const T*)(srcmat.data + srcmat.step*y); DT* dst = (DT*)(dstmat.data + dstmat.step*y); int x = 0; for(; x <= size.width - 4; x += 4 ) { DT t0, t1; t0 = op(src[x]*scale + shift); t1 = op(src[x+1]*scale + shift); dst[x] = t0; dst[x+1] = t1; t0 = op(src[x+2]*scale + shift); t1 = op(src[x+3]*scale + shift); dst[x+2] = t0; dst[x+3] = t1; } for( ; x &lt; size.width; x++ ) dst[x] = op(src[x]*scale + shift); } }
      
      





これは、char、short、float、doubleで動作する単純なテンプレート関数です。

その作成者は、4の内部ループを展開し、残りのデータテールを個別に処理することにより、コンパイラがSSEベクトル化を支援することを決定しました。

最新のコンパイラ(Windows用)は、著者の意図に従って最適化されたコードを生成すると思いますか?

/ QxSSE2スイッチを使用して、Intel Compiler 12.0を使用してこのコードをコンパイルして確認しましょう(他のSSExおよびAVXオプションを使用しても同じ結果が得られることが確認されています)



そして、結果はまったく予想外です。 コンパイラーの出力にあるアセンブラーのリストは、展開されたループがベクトル化されていないことを最終的に示しています。 コンパイラーはSSE命令を生成しますが、ベクトルではなくスカラーのみを生成します。 しかし、残りのデータ、つまり展開されていないサイクルで1〜3個のデータ要素のみを含む「テール」は、完全なプログラムに従ってベクトル化されます。



ループのアンラップを削除する場合:
 for( int y = 0; y < size.height; y++ ) { const T* src = (const T*)(srcmat.data + srcmat.step*y); DT* dst = (DT*)(dstmat.data + dstmat.step*y); int x = 0; for( ; x < size.width; x++ ) dst[x] = op(src[x]*scale + shift); }
      
      





...アセンブラーをもう一度見てみましょう(怖くはしません)、すべてのデータ型に対してサイクルが完全にベクトル化され、間違いなく生産性が向上することがわかります。



結論より多くの作業-より少ないパフォーマンス。 少ない仕事で、もっと。 それは常にそうです。



/ arch:SSE2スイッチを使用したMicrosoftコンパイラ、Visual Studio 2010および2008は、上記のコードを展開形式または最小化形式でベクトル化しません。 生成されたコードは、どちらの場合も外観とパフォーマンスの両方で非常に似ています。 つまり、Intelコンパイラーのループの展開が有害である場合、Microsoftにとっては役に立たないだけです:)。



しかし、ループの展開を維持したい場合はどうでしょうか? メモリとして高価ですが、ベクトル化も必要ですか?



次に、以下に示すようにIntelコンパイラプラグマを使用します。

#pragma simd
 for(x=0; x <= size.width - 4; x += 4 ) { DT t0, t1; t0 = op(src[x]*scale + shift); t1 = op(src[x+1]*scale + shift); dst[x] = t0; dst[x+1] = t1; t0 = op(src[x+2]*scale + shift); t1 = op(src[x+3]*scale + shift); dst[x+2] = t0; dst[x+3] = t1; }
      
      





#pragma novector
 for( ; x <size.width; x++ ) dst[x] = op(src[x]*scale + shift); }
      
      







そして最後の1つ。 サイクルの展開自体は、パフォーマンスにプラスの影響を与える可能性があります。 しかし、第一に、ベクトル化から得られる可能性は依然としてこのプラスの効果を上回ります。そして、第二に、展開をコンパイラーに任せることができ、ベクトル化はこの影響を受けません。 とりわけ、10月27日のウェビナーでこのトピックに触れる予定です。



All Articles