2017-02-02 13 views
0

私はベクトルのすべての要素を追加するためのインライン関数を実装しましたが、非SIMD以外のものより高速ではありません。なぜ私のAVX2水平加算機能は非SIMD加算より高速ではありませんか?

宣言:これらは、ベクター内のすべてのint型の値を追加するための私の2つの方法があり

#define N 128 
#define M N 
int __attribute__((aligned(32)))temp8[8]; 
__m256i vec; 
int __attribute__((aligned(32))) c_result[N][M]; 

まず、非SIMDバージョンは次のとおりです。

_mm256_store_si256((__m256i *)&temp8[0] , vec); 
    c_result[i][j]= temp8[0]+temp8[1]+temp8[2]+temp8[3]+temp8[4]+temp8[5]+temp8[6]+temp8[7]; 

第二に、AVX2版:

c_result[i][j] =_mm256_hadd2_epi32(vec); 

私は、このようにhadd2を実装:

// my horizontal addition of epi32 
    inline int _mm256_hadd2_epi32(__m256i a) 
    { 
    __m256i a_hi; 
    a_hi = _mm256_permute2x128_si256(a, a, 1); //maybe 1 should be 4 
    a = _mm256_hadd_epi32(a, a_hi); 
    a = _mm256_hadd_epi32(a, a); 
    a = _mm256_hadd_epi32(a, a); 
    return _mm256_extract_epi32(a,0); 
    } 

私はgccLinux-mintskylakeマイクロアーキテクチャを使用します。

私はそれは以下の理由であるかもしれない推測: 特に要素の順序を変更するには、少なくとも1つのサイクルを必要とする順列に制限ベクトル実行ユニットとは対照的にしっかりとそれらを追加しますSkylakeマイクロアーキテクチャのマイクロアーキテクチャの整数値の4 ALUがあります。次にhadd命令が続きます。問題は、私は何かが欠けているか、すべての要素を追加するためにSIMDを使う必要はないということですか?

更新日: MULプログラムをリポジトリhereに追加しました。行列の乗算のコード全体にアクセスできました。私が非SIMDバージョンを使用する場合、経過時間は201nsになり、SIMDバージョンでは210nsがかかります。

+1

一歩を踏み出して、このような水平操作を行う必要がある理由を質問してください。本当にパフォーマンス重大なボトルネックの一部になっているのであれば、通常は、ループの後に水平操作を行うだけで済みます。削減の最終ステップとして、通常は重要な部分ではありませんが、パフォーマンスに賢明です。 –

+0

@PaulR、そうです。このようにボトルネックセクションではありません。そして、私は、非SIMDとAVX2バージョンのパフォーマンスを自分自身でテストしたいと思っていました。もちろん、これは研究目的と教育目的です。しかし、その答えが私を助けます。ボトルネックセクションで3,5,7および9の隣接要素を水平方向に追加する必要があるConvolution Matrix Kernelを実装したためです。 – Martin

+1

最適化はそれより少し複雑です。 Agner Fogマニュアルへのリンクについては、x86タグwikiを参照してください。フロントエンド、生成されたuops、ポート、遅延、スループット、依存関係を考慮する必要があります。あなたは* perf *でコードをプロファイリングしましたか? –

答えて

2

直感は、このステップ...

temp8[0]+temp8[1]+temp8[2]+temp8[3]+temp8[4]+temp8[5]+temp8[6]+temp8[7] 

は、ベクトル化が加速する必要があること、高価な部分があるが、それはおそらく間違っているかもしれません。追加は単一のmuopであり、レジスタ(メモリではなく)を操作する限り、最新のx64マシンで4サイクル実行できます。だから、理論的には、お使いのプロセッサは

サイクル1

temp8[0]+temp8[1] 
temp8[2]+temp8[3] 
temp8[4]+temp8[5] 
temp8[6]+temp8[7] 

サイクル2

(temp8[0]+temp8[1])+(temp8[2]+temp8[3]) 
(temp8[4]+temp8[5])+(temp8[6]+temp8[7]) 

...これを行うことができますし、余裕の容量で、サイクル3の答えを得ます。 (私たちのプロセッサはスーパースカラであり、順不同のパイプラインを持っているので、これは魔法のように起こります)

ベクトル化アプローチはどれくらい早くできますか?あなたは私たちに答えを与えた...

a = _mm256_hadd_epi32(a, a_hi); 
a = _mm256_hadd_epi32(a, a); 
a = _mm256_hadd_epi32(a, a); 

私たちは、3回...もちろん、それは多分、安価に見える...しかし_mm256_hadd_epi32固有のPHADD命令である(下におそらく何を認識することができます〜3 2サイクルごとに1命令)。重要な点は、プロセッサが複数のスカラー加算を同時に行うことができる間に、プロセッサが_mm256_hadd_epi32組み込み関数のいくつかを同時に実行できないことです。したがって、より速いのは技術的な問題になります。

私の答えをまとめるには...ベクトル化が安価な命令のスーパースカラ実行(追加)に向いているので、このインスタンスで役立つベクトルを期待するべきではありません。

付録。このコード

_mm256_store_si256((__m256i *)&temp8[0] , vec); 
    c_result[i][j]= temp8[0]+temp8[1]+temp8[2]+temp8[3]+temp8[4]+temp8[5]+temp8[6]+temp8[7]; 

おそらくあなたが思うようにコンパイルされません。我々は追加を認識

​​3210

にこれをダウンコンパイル、私たちは関数として

uint32_t hadd32(__m256i vector) { 
    uint32_t buffer[sizeof(__m256i)/sizeof(uint32_t)]; 
_mm256_store_si256((__m256i *)buffer , vector); 
uint32_t answer = buffer[0]+buffer[1]+buffer[2]+buffer[3]+buffer[4]+buffer[5]+buffer[6]+buffer[7]; 
return answer; 
} 

いくつかのコンパイラ(打ち鳴らす、GCC 7)、それを洗い流しましょう、しかし、ここで完全に無視されて、一時的なバッファvpextrdコールが優先されます。ここでの教訓は、常に生成されたアセンブリを見ることです。

関連する問題