13

Intel の Software Developer Manual (14.9 節) によると、AVX はメモリ アクセスのアライメント要件を緩和しました。データが処理命令に直接ロードされた場合。

vaddps ymm0,ymm0,YMMWORD PTR [rax]

ロード アドレスを揃える必要はありません。ただし、次のような専用のアライメントされたロード命令が使用されている場合

vmovaps ymm0,YMMWORD PTR [rax]

ロードアドレスは (32 の倍数に) アラインされている必要があります。アラインされていない場合、例外が発生します。

私を混乱させているのは、組み込み関数からの自動コード生成です。私の場合は gcc/g++ (4.6.3、Linux) によるものです。次のテストコードを見てください。

#include <x86intrin.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#define SIZE (1L << 26)
#define OFFSET 1

int main() {
  float *data;
  assert(!posix_memalign((void**)&data, 32, SIZE*sizeof(float)));
  for (unsigned i = 0; i < SIZE; i++) data[i] = drand48();
  float res[8]  __attribute__ ((aligned(32)));
  __m256 sum = _mm256_setzero_ps(), elem;
  for (float *d = data + OFFSET; d < data + SIZE - 8; d += 8) {
    elem = _mm256_load_ps(d);
    // sum = _mm256_add_ps(elem, elem);
    sum = _mm256_add_ps(sum, elem);
  }
  _mm256_store_ps(res, sum);
  for (int i = 0; i < 8; i++) printf("%g ", res[i]); printf("\n");
  return 0;
}

(はい、アラインされていないアドレスに対してアラインされたロードを使用しているため、コードに問題があることはわかっていますが、ご容赦ください...)

コードをコンパイルします

g++ -Wall -O3 -march=native -o memtest memtest.C

AVXを搭載したCPU上。を使用してg ++によって生成されたコードを確認すると

objdump -S -M intel-mnemonic memtest | more

コンパイラはアラインされたロード命令を生成せず、ベクトル加算命令でデータを直接ロードすることがわかります。

vaddps ymm0,ymm0,YMMWORD PTR [rax]

メモリ アドレスがアラインされていない (OFFSET が 1) 場合でも、コードは問題なく実行されます。vaddps はアラインされていないアドレスを許容するため、これは明らかです。

2 番目の加算組み込み関数の行のコメントを外すと、vaddps はメモリ ソース オペランドを 1 つしか持てないため、コンパイラはロードと加算を融合できず、次のように生成します。

vmovaps ymm0,YMMWORD PTR [rax]
vaddps ymm1,ymm0,ymm0
vaddps ymm0,ymm1,ymm0

そして、専用のアラインされたロード命令が使用されているため、プログラム seg-fault が発生しますが、メモリ アドレスはアラインされていません。(ちなみに、_mm256_loadu_ps を使用するか、OFFSET を 0 に設定すると、プログラムはセグメンテーション フォールトしません。)

私の謙虚な意見では、これにより、プログラマーはコンパイラーに翻弄され、動作が部分的に予測不能になります。

私の質問は次のとおりです。C コンパイラに、処理命令 (vaddps など) で直接ロードを生成させるか、専用のロード命令 (vmovaps など) を生成させる方法はありますか?

4

2 に答える 2

7

組み込み関数を使用してロードの折りたたみを明示的に制御する方法はありません。これは組み込み関数の弱点だと思います。折りたたみを明示的に制御したい場合は、アセンブリを使用する必要があります。

以前のバージョンの GCC では、整列または非整列のロードを使用して折り畳みをある程度制御できました。ただし、そうではないようです (GCC 4.9.2)。たとえば、AddDot4x4_vec_block_8wide ここの関数では、負荷が折りたたまれています

vmulps  ymm9, ymm0, YMMWORD PTR [rax-256]
vaddps  ymm8, ymm9, ymm8

ただし、GCCの以前のバージョンでは、ロードは折りたたまれていませんでした。

vmovups ymm9, YMMWORD PTR [rax-256]
vmulps  ymm9, ymm0, ymm9
vaddps  ymm8, ymm8, ymm9

正しい解決策は、明らかに、データが整列されていることがわかっていて、折りたたみ使用アセンブリを明示的に制御したい場合にのみ、整列されたロードを使用することです。

于 2015-06-28T10:23:03.273 に答える
4

Z bosonの答えに加えて、コンパイラがメモリ領域が整列していると想定しているために問題が発生する可能性があることがわかります(__attribute__ ((aligned(32)))配列をマークするため)。スタックは 16 バイトしかアラインされていないため、ランタイムでは、この属性はスタック上の値に対して機能しない可能性があります (このバグを参照してください。このバグは、この記事の執筆時点では未解決ですが、一部の修正により gcc 4.6 に組み込まれています)。コンパイラには、組み込み関数を実装する命令を選択する権利があるため、メモリ負荷を計算命令にフォールドするvmovaps場合としない場合があり、フォールディングが発生しない場合に使用する権利もあります (前述のように、 、メモリ領域は整列されているはずです)。

および(こちらmainを参照)を指定することにより、エントリ時にコンパイラにスタックを 32 バイトに再調整するように強制することができますが、パフォーマンスのオーバーヘッドが発生します。-mstackrealign-mpreferred-stack-boundary=5

于 2016-12-11T10:15:38.453 に答える