19

2 つのバッファーを追加して結果を保存するとします。両方のバッファはすでに 16 バイトでアラインされて割り当てられています。その方法の例を 2 つ見つけました。

1 つ目は、_mm_load を使用してバッファから SSE レジスタにデータを読み込み、加算操作を実行して結果レジスタに格納します。今までなら、そうしていただろう。

void _add( uint16_t * dst, uint16_t const * src, size_t n )
{
  for( uint16_t const * end( dst + n ); dst != end; dst+=8, src+=8 )
  {
    __m128i _s = _mm_load_si128( (__m128i*) src );
    __m128i _d = _mm_load_si128( (__m128i*) dst );

    _d = _mm_add_epi16( _d, _s );

    _mm_store_si128( (__m128i*) dst, _d );
  }
}

2 番目の例では、ロード/ストア操作を行わずに、メモリ アドレスに対して直接追加操作を実行しました。両方の縫い目が正常に機能します。

void _add( uint16_t * dst, uint16_t const * src, size_t n )
{
  for( uint16_t const * end( dst + n ); dst != end; dst+=8, src+=8 )
  {
    *(__m128i*) dst = _mm_add_epi16( *(__m128i*) dst, *(__m128i*) src );
  }
}

したがって、問題は、2 番目の例が正しいか、または何らかの副作用があるかどうか、およびいつロード/ストアを使用するかが必須であるということです。

ありがとう。

4

3 に答える 3

13

どちらのバージョンも問題ありません。生成されたコードを見ると、PADDW(aka _mm_add_epi16)はメモリから直接2番目の引数しか取得できないため、2番目のバージョンでもベクトルレジスタに少なくとも1つのロードが生成されていることがわかります。

実際には、ほとんどの重要なSIMDコードは、データの読み込みと保存の間に、1回の追加よりもはるかに多くの操作を実行します。したがって、一般に、を使用して最初にデータをベクトル変数(レジスタ)に読み込み、レジスタ_mm_load_XXXに対してすべてのSIMD操作を実行する必要があります。 、次に結果を。を介してメモリに保存します_mm_store_XXX

于 2012-06-14T14:08:31.717 に答える
7

movdqu主な違いは、2 番目のバージョンでは、ポインタが 16 バイトでアラインされていることを証明できない場合、コンパイラがアラインされていないロード (など) を生成することです。周囲のコードによっては、このプロパティがコンパイラによって証明できるコードを記述することさえできない場合があります。

それ以外の場合、違いはありません。コンパイラは、2 つのロードと追加を 1 つのロードにマングルし、有用であると判断した場合はメモリからの追加を行うか、ロードと追加の命令を 2 つに分割するほどスマートです。

C ++を使用している場合は、次のように書くこともできます

void _add( __v8hi* dst, __v8hi const * src, size_t n )
{
    n /= 8;
    for( int i=0; i<n; ++i )
        d[i| += s[i];
}

__v8hivector of 8 half integersまたはの略語typedef short __v8hi __attribute__ ((__vector_size__ (16)));です。各ベクトル タイプには、gcc と icc の両方でサポートされている同様の事前定義されたタイプがあります。

これにより、ほぼ同じコードが得られますが、さらに高速になる場合とそうでない場合があります。しかし、それはより読みやすく、おそらくコンパイラによってさえ、AVX に簡単に拡張できると主張することができます。

于 2012-06-15T07:23:40.467 に答える