ベクトル・プログラミング: インテル® SSE4.2 からインテル® AVX2 への変換例

特集

この記事は、インテル® デベロッパー・ゾーンに公開されている「Vector programming. SSE4.2 to AVX2 conversion examples」(https://software.intel.com/en-us/blogs/2015/01/15/vector-programming-sse42-to-avx2-conversion-examples) の日本語参考訳です。


この記事では、インテル® ストリーミング SIMD 拡張命令 4.2 (インテル® SSE4.2) アセンブリーからインテル® アドバンスト・ベクトル・エクステンション 2 (インテル® AVX2) への変換方法を紹介し (インテル® AVX2 によるプログラミングを参考にして)、パフォーマンスへの影響を考察します。

  • 簡単な例プリフィックス “v” を追加して、レジスターを “xmm” から “ymm” に変更

次のループを考えてみましょう:

for (i = 0; i < 1024; i++)
  a[i] += b[i];

“a” と “b” が符号無し char 配列である場合、インテル® SSE4.2 のアセンブラーは次のようになります:

.L2:
        movdqa a(%rax), %xmm0
        paddb b(%rax), %xmm0
        addq $16, %rax
        movaps %xmm0, a-16(%rax)
        cmpq $1024, %rax
        jne .L2

このコードのインテル® AVX2 への変換は簡単です (“v” プリフィックスを追加し、”xmm” を “ymm” に置き換えて、ロード/ストアのステップを 16 から 32 に変更します):

.L2:
        vmovdqa a(%rax), %ymm0
        vpaddb b(%rax), %ymm0, %ymm0
        addq $32, %rax
        vmovaps %ymm0, a-32(%rax)
        cmpq $1024, %rax
        jne .L2

最大 1.9 倍のゲインが期待できます!この手法は、pmul、psub、pmin、などにも有効です。

VPACK

次のループを考えてみましょう:

for (i = 0; i < 1024; i++)
  a[i] = b[2 * i];

“a” と “b” が符号無し char 配列である場合、インテル® SSE4.2 (%xmm1 は 0xff00ff00ff00ff00ff00ff00ff00ff) 向けのソリューションの 1 つとして次が考えられます:

.L2:
        movdqa b(%rax,%rax), %xmm0
        movdqa b+16(%rax,%rax), %xmm2
        addq $16, %rax
        pand %xmm1, %xmm0
        pand %xmm1, %xmm2
        packuswb %xmm2, %xmm0
        movaps %xmm0, a-16(%rax)
        cmpq $1024, %rax
        jne .L2

インテル® AVX2 への変換は、前述の簡単な例と同じ置き換えを行い、“vpermq” 命令を追加します (%ymm1 の定数は 2 倍の長さになります):

.L2:
        vmovdqa b(%rax,%rax), %ymm0
        vmovdqa b+32(%rax,%rax), %ymm2
        addq $32, %rax
        vpand %ymm1, %ymm0, %ymm0
        vpand %ymm1, %ymm2, %ymm2
        vpackuswb %ymm2, %ymm0, %ymm0
        vpermq $216, %ymm0, %ymm0
        vmovaps %ymm0, a-32(%rax)
        cmpq $1024, %rax
        jne .L2

最大 1.5 倍のゲインが期待できます!

VPUNPCK{L,H}

次のループを考えてみましょう:

for (i = 0; i < 1024; i++)
    {
       b[2 * i] = a[i];
       b[2 * i + 1] = 2 * a[i];
    }

インテル® SSE4.2 向けのソリューションの 1 つとして次が考えられます:

.L2:
        movdqa  a(%rax), %xmm0
        movdqa  %xmm0, %xmm1
        movdqa  %xmm0, %xmm2
        paddb   %xmm0, %xmm1
        punpcklbw       %xmm1, %xmm2
        punpckhbw       %xmm1, %xmm0
        movaps  %xmm2, b(%rax,%rax)
        movaps  %xmm0, b+16(%rax,%rax)
        addq    $16, %rax
        cmpq    $1024, %rax
        jne     .L2

インテル® AVX2 への変換は、前述の簡単な例と同じ置き換えを行い、2 つの “vpermi128” 命令を追加します:

.L2:
        vmovdqa a(%rax), %ymm0
        vmovdqa %ymm0, %ymm1
        vmovdqa %ymm0, %ymm2
        vpaddb  %ymm0, %ymm1, %ymm1
        vpunpcklbw      %ymm1, %ymm2, %ymm2
        vpunpckhbw      %ymm1, %ymm0, %ymm0
        vperm2i128 $32, %ymm0, %ymm2, %ymm1
        vperm2i128 $49, %ymm0, %ymm2, %ymm0
        vmovaps %ymm1, b(%rax,%rax)
        vmovaps %ymm0, b+32(%rax,%rax)
        addq    $32, %rax
        cmpq    $1024, %rax
        jne     .L2

この場合パフォーマンスはほぼ同一 (約 3% ゲイン) です。

この計測で利用したプロセッサー: “Haswell: インテル® Core™ i7-4770K プロセッサー @ 3.50GHz”

このような変換は、常に適切なインテル® AVX2 コードを生成するわけではないことに注意してください。しかし、コードを変換することで一般的に高速化できることが分かります。ループにより多くの計算 (置換なし) を追加すると、パフォーマンスは 2 倍近くになります。

コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください

開発コード名

タイトルとURLをコピーしました