この記事は、インテル® デベロッパー・ゾーンに公開されている「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 倍近くになります。
コンパイラーの最適化に関する詳細は、最適化に関する注意事項を参照してください
✝ 開発コード名