35

次の 2 つのスニペットを検討してください。

#define ALIGN_BYTES 32
#define ASSUME_ALIGNED(x) x = __builtin_assume_aligned(x, ALIGN_BYTES)

void fn0(const float *restrict a0, const float *restrict a1,
         float *restrict b, int n)
{
    ASSUME_ALIGNED(a0); ASSUME_ALIGNED(a1); ASSUME_ALIGNED(b);

    for (int i = 0; i < n; ++i)
        b[i] = a0[i] + a1[i];
}

void fn1(const float *restrict *restrict a, float *restrict b, int n)
{
    ASSUME_ALIGNED(a[0]); ASSUME_ALIGNED(a[1]); ASSUME_ALIGNED(b);

    for (int i = 0; i < n; ++i)
        b[i] = a[0][i] + a[1][i];
}

関数をコンパイルするとgcc-4.7.2 -Ofast -march=native -std=c99 -ftree-vectorizer-verbose=5 -S test.c -Wall、GCC が 2 番目の関数のエイリアシング チェックを挿入することがわかりました。

fn1の結果のアセンブリが のアセンブリと同じになるように、これを防ぐにはどうすればよいfn0ですか? (パラメータの数が 3 から、たとえば 30 に増えると、引数を渡すアプローチ ( fn0) は扱いにくくなり、アプローチのエイリアシング チェックの数はfn1ばかげたものになります。)

アセンブリ (x86-64、AVX 対応チップ); .LFB10 でのエイリアシング クラフト

fn0:
.LFB9:
    .cfi_startproc
    testl   %ecx, %ecx
    jle .L1
    movl    %ecx, %r10d
    shrl    $3, %r10d
    leal    0(,%r10,8), %r9d
    testl   %r9d, %r9d
    je  .L8
    cmpl    $7, %ecx
    jbe .L8
    xorl    %eax, %eax
    xorl    %r8d, %r8d
    .p2align 4,,10
    .p2align 3
.L4:
    vmovaps (%rsi,%rax), %ymm0
    addl    $1, %r8d
    vaddps  (%rdi,%rax), %ymm0, %ymm0
    vmovaps %ymm0, (%rdx,%rax)
    addq    $32, %rax
    cmpl    %r8d, %r10d
    ja  .L4
    cmpl    %r9d, %ecx
    je  .L1
.L3:
    movslq  %r9d, %rax
    salq    $2, %rax
    addq    %rax, %rdi
    addq    %rax, %rsi
    addq    %rax, %rdx
    xorl    %eax, %eax
    .p2align 4,,10
    .p2align 3
.L6:
    vmovss  (%rsi,%rax,4), %xmm0
    vaddss  (%rdi,%rax,4), %xmm0, %xmm0
    vmovss  %xmm0, (%rdx,%rax,4)
    addq    $1, %rax
    leal    (%r9,%rax), %r8d
    cmpl    %r8d, %ecx
    jg  .L6
.L1:
    vzeroupper
    ret
.L8:
    xorl    %r9d, %r9d
    jmp .L3
    .cfi_endproc
.LFE9:
    .size   fn0, .-fn0
    .p2align 4,,15
    .globl  fn1
    .type   fn1, @function
fn1:
.LFB10:
    .cfi_startproc
    testq   %rdx, %rdx
    movq    (%rdi), %r8
    movq    8(%rdi), %r9
    je  .L12
    leaq    32(%rsi), %rdi
    movq    %rdx, %r10
    leaq    32(%r8), %r11
    shrq    $3, %r10
    cmpq    %rdi, %r8
    leaq    0(,%r10,8), %rax
    setae   %cl
    cmpq    %r11, %rsi
    setae   %r11b
    orl %r11d, %ecx
    cmpq    %rdi, %r9
    leaq    32(%r9), %r11
    setae   %dil
    cmpq    %r11, %rsi
    setae   %r11b
    orl %r11d, %edi
    andl    %edi, %ecx
    cmpq    $7, %rdx
    seta    %dil
    testb   %dil, %cl
    je  .L19
    testq   %rax, %rax
    je  .L19
    xorl    %ecx, %ecx
    xorl    %edi, %edi
    .p2align 4,,10
    .p2align 3
.L15:
    vmovaps (%r9,%rcx), %ymm0
    addq    $1, %rdi
    vaddps  (%r8,%rcx), %ymm0, %ymm0
    vmovaps %ymm0, (%rsi,%rcx)
    addq    $32, %rcx
    cmpq    %rdi, %r10
    ja  .L15
    cmpq    %rax, %rdx
    je  .L12
    .p2align 4,,10
    .p2align 3
.L20:
    vmovss  (%r9,%rax,4), %xmm0
    vaddss  (%r8,%rax,4), %xmm0, %xmm0
    vmovss  %xmm0, (%rsi,%rax,4)
    addq    $1, %rax
    cmpq    %rax, %rdx
    ja  .L20
.L12:
    vzeroupper
    ret
.L19:
    xorl    %eax, %eax
    jmp .L20
    .cfi_endproc
4

4 に答える 4

1

エイリアシングのチェックを停止するようにコンパイラに指示する方法はありません。

行を追加してください:

#pragma GCC ivdep

ベクトル化するループの直前に、さらに情報が必要な場合は以下をお読みください。

https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Loop-Specific-Pragmas.html

于 2015-01-19T12:13:51.107 に答える
0

私のマシンでは GCC 4.7 で結果を再現できないため、前もってお詫びしますが、解決策が 2 つあります。

  1. typedef を使用して* restrict * restrict適切に構成します。LLVM コンパイラを開発した元同僚によると、これはtypedefC のプリプロセッサのように動作する唯一の例外であり、必要なアンチエイリアシング動作を可能にするために存在します。

    以下でこれを試みましたが、成功したかどうかはわかりません。私の試みの事実を注意深く確認してください。

  2. C99 可変長配列 (VLA) での restrict 修飾子の使用に対する回答で説明されている構文を使用します。

    以下でこれを試みましたが、成功したかどうかはわかりません。私の試みの事実を注意深く確認してください。

実験を実行するために使用したコードを次に示しますが、提案のいずれかが希望どおりに機能したかどうかを最終的に判断することはできませんでした.

#define ALIGN_BYTES 32
#define ASSUME_ALIGNED(x) x = __builtin_assume_aligned(x, ALIGN_BYTES)

void fn0(const float *restrict a0, const float *restrict a1,
         float *restrict b, int n)
{
    ASSUME_ALIGNED(a0); ASSUME_ALIGNED(a1); ASSUME_ALIGNED(b);

    for (int i = 0; i < n; ++i)
        b[i] = a0[i] + a1[i];
}

#if defined(ARRAY_RESTRICT)
void fn1(const float *restrict a[restrict], float * restrict b, int n)
#elif defined(TYPEDEF_SOLUTION)
typedef float * restrict frp;
void fn1(const frp *restrict a, float *restrict b, int n)
#else
void fn1(const float *restrict *restrict a, float *restrict b, int n)
#endif
{
    //ASSUME_ALIGNED(a[0]); ASSUME_ALIGNED(a[1]); ASSUME_ALIGNED(b);

    for (int i = 0; i < n; ++i)
        b[i] = a[0][i] + a[1][i];
}

繰り返しますが、この回答の中途半端な性質についてお詫び申し上げます。試みたが成功しなかったために私に反対票を投じないでください。

于 2015-04-28T21:50:42.347 に答える
0

これは役に立ちますか?

void fn1(const float **restrict a, float *restrict b, int n)
{
    const float * restrict a0 = a[0];
    const float * restrict a1 = a[1];

    ASSUME_ALIGNED(a0); ASSUME_ALIGNED(a1); ASSUME_ALIGNED(b);

    for (int i = 0; i < n; ++i)
        b[i] = a0[i] + a1[i];
}

編集:2回目の試行:)。http://locklessinc.com/articles/vectorize/からの情報を使用

gcc --fast-math ...

于 2013-03-25T12:33:41.070 に答える
0

では、国旗はどうでしょうか。

-fno-strict-aliasing

?

私が理解しているように、このチェックをオフにする方法を知りたいだけですか?それだけなら、gcc コマンドラインへのこのパラメーターが役立つはずです。

編集:

あなたのコメントに加えて: const 型の制限ポインタを使用することは禁止されていませんか?

これは ISO/IEC 9899 (6.7.3.1 制限の正式な定義) からのものです。

1.

D を、オブジェクト P を型 T への制限修飾付きポインターとして指定する手段を提供する通常の識別子の宣言とします。

4.

B を実行するたびに、L を P に基づく &L を持つ任意の左辺値とする。L が指定するオブジェクト X の値にアクセスするために L が使用され、X も (何らかの方法で) 変更される場合、次の要件が適用される。 : T は const 修飾されてはなりません。X の値にアクセスするために使用される他のすべての左辺値も、P に基づくアドレスを持つものとします。X を変更するすべてのアクセスは、この節の目的のために、P を変更するものとも見なされます。ブロック B2 に関連付けられた別の制限付きポインター オブジェクト P2 に基づくポインター式 E の値が P に割り当てられている場合、B2 の実行は B の実行前に開始されるか、または B2 の実行はブロック B2 の実行前に終了します。割り当て。これらの要件が満たされない場合、動作は未定義です。

そして、レジスターと同じように、もっと興味深い点は次のとおりです。

6.

翻訳者は、restrict の使用によるエイリアシングの影響の一部またはすべてを自由に無視できます。

したがって、gcc に強制的に実行させるコマンド パラメーターが見つからない場合は、おそらく不可能です。標準では、そうするオプションを指定する必要がないからです。

于 2013-08-09T08:02:51.647 に答える