アセンブラコードで見るC++ Composer XEの強力な最適化機能 3ページ
さて、ここではSSEを使用した場合と使用しない場合でのパフォーマンス差をチェックしたが、実はVisual Studioでもオプション設定を行うことでSSEを利用するコードを出力できる。Visual Studio 2005/2008ではMMX/SSE/SSE2命令を使用した最適化が、Visual Studio 2010ではAVXを使用した最適化がサポートされている。たとえばSSE2を利用する場合、「/arch:sse2」オプションを使用すればよい。実際にVisual Studio 2010でSSE2を利用するように設定して生成したアセンブラコードが次のリスト4だ。
リスト4 Visual Studio 2008でSSE2を利用するよう設定し生成したアセンブラコード
; 142 : d = 0.0;
xorps xmm0, xmm0
; 143 : for (i = 0; i < 128; i++) {
xor eax, eax
npad 3
$LL3@main:
; 144 : d += a[i] * b[i];
movss xmm1, DWORD PTR _b$[esp+eax+1088]
movss xmm2, DWORD PTR _a$[esp+eax+1088]
cvtps2pd xmm1, xmm1
cvtss2sd xmm0, xmm0
cvtps2pd xmm2, xmm2
mulsd xmm1, xmm2
addsd xmm1, xmm0
movss xmm2, DWORD PTR _b$[esp+eax+1092]
xorps xmm0, xmm0
cvtpd2ps xmm0, xmm1
movss xmm1, DWORD PTR _a$[esp+eax+1092]
cvtps2pd xmm0, xmm0
cvtps2pd xmm1, xmm1
cvtps2pd xmm2, xmm2
mulsd xmm1, xmm2
addsd xmm0, xmm1
movss xmm1, DWORD PTR _a$[esp+eax+1096]
movss xmm2, DWORD PTR _b$[esp+eax+1096]
cvtpd2ps xmm0, xmm0
cvtss2sd xmm0, xmm0
cvtps2pd xmm1, xmm1
cvtps2pd xmm2, xmm2
mulsd xmm1, xmm2
movss xmm2, DWORD PTR _b$[esp+eax+1100]
addsd xmm0, xmm1
movss xmm1, DWORD PTR _a$[esp+eax+1100]
cvtpd2ps xmm0, xmm0
cvtss2sd xmm0, xmm0
cvtps2pd xmm1, xmm1
cvtps2pd xmm2, xmm2
mulsd xmm1, xmm2
movss xmm2, DWORD PTR _b$[esp+eax+1104]
addsd xmm0, xmm1
movss xmm1, DWORD PTR _a$[esp+eax+1104]
cvtpd2ps xmm0, xmm0
cvtss2sd xmm0, xmm0
cvtps2pd xmm1, xmm1
cvtps2pd xmm2, xmm2
mulsd xmm1, xmm2
movss xmm2, DWORD PTR _b$[esp+eax+1108]
addsd xmm0, xmm1
movss xmm1, DWORD PTR _a$[esp+eax+1108]
cvtpd2ps xmm0, xmm0
cvtss2sd xmm0, xmm0
cvtps2pd xmm1, xmm1
cvtps2pd xmm2, xmm2
mulsd xmm1, xmm2
movss xmm2, DWORD PTR _b$[esp+eax+1112]
addsd xmm0, xmm1
movss xmm1, DWORD PTR _a$[esp+eax+1112]
cvtpd2ps xmm0, xmm0
cvtss2sd xmm0, xmm0
cvtps2pd xmm1, xmm1
cvtps2pd xmm2, xmm2
mulsd xmm1, xmm2
movss xmm2, DWORD PTR _b$[esp+eax+1116]
addsd xmm0, xmm1
movss xmm1, DWORD PTR _a$[esp+eax+1116]
cvtpd2ps xmm0, xmm0
cvtss2sd xmm0, xmm0
cvtps2pd xmm1, xmm1
cvtps2pd xmm2, xmm2
add eax, 32
mulsd xmm1, xmm2
addsd xmm0, xmm1
cvtpd2ps xmm0, xmm0
cmp eax, 512
jl $LL3@main
; 145 : }
; 146 :
; 147 : printf("%f\n", d);
sub esp, 8
cvtss2sd xmm0, xmm0
movsd QWORD PTR [esp], xmm0
push OFFSET $SG5712
call _printf
こちらの場合、「xmm0」~「xmm2」があることからも分かるとおり、確かにSSEを利用したコードが出力されている(太字がSSE命令)。ただし、インテル C++ Composer XE 2011で生成されたコードとは異なり、乗算/加算にはパックド命令は使用されておらず、同時に1つのデータしか処理していない。実行時間も9.800秒と、SSE2を利用しない場合よりもわずかではあるが劣る結果となった。
なお、Visual StudioではSSE/SSE2、およびインテル AVXのみがサポートされているが、インテル C++ Composer XE 2011ではSSE2/SSE3/SSSE3/SSE4.1/SSE4.2およびインテル AVXがサポートされている。インテル AVXが実装されたCPUはまだリリースされていないものの、コードの生成自体は可能だ。実際にリスト1のコードをインテル AVX対応CPU向けの設定でコンパイルして生成されたアセンブラコードが次のリスト5である。
リスト5 インテル AVXを使用するアセンブラコード
;;; d = 0.0;
;;; for (i = 0; i < 128; i++) {
;;; d += a[i] * b[i];
lea eax, DWORD PTR [1184+esp]
and eax, 31
mov DWORD PTR [128+esp], edi
mov esi, eax
mov edi, edx
.B1.7:
mov ecx, esi
.B1.12:
vxorps ymm0, ymm0, ymm0
vxorpd ymm1, ymm1, ymm1
.B1.13:
vmovupd xmm2, XMMWORD PTR [136+esp+ecx*8]
vmovupd xmm5, XMMWORD PTR [168+esp+ecx*8]
vinsertf128 ymm3, ymm2, XMMWORD PTR [152+esp+ecx*8], 1
vinsertf128 ymm6, ymm5, XMMWORD PTR [184+esp+ecx*8], 1
vmulpd ymm4, ymm3, YMMWORD PTR [1184+esp+ecx*8]
vmulpd ymm7, ymm6, YMMWORD PTR [1216+esp+ecx*8]
vmovupd xmm2, XMMWORD PTR [200+esp+ecx*8]
vmovupd xmm5, XMMWORD PTR [232+esp+ecx*8]
vaddpd ymm0, ymm0, ymm4
vaddpd ymm1, ymm1, ymm7
vinsertf128 ymm3, ymm2, XMMWORD PTR [216+esp+ecx*8], 1
vinsertf128 ymm6, ymm5, XMMWORD PTR [248+esp+ecx*8], 1
vmulpd ymm4, ymm3, YMMWORD PTR [1248+esp+ecx*8]
vmulpd ymm7, ymm6, YMMWORD PTR [1280+esp+ecx*8]
vaddpd ymm0, ymm0, ymm4
vaddpd ymm1, ymm1, ymm7
add ecx, 16
cmp ecx, 128
jb .B1.13
.B1.14:
vaddpd ymm0, ymm0, ymm1
vextractf128 xmm1, ymm0, 1
vaddpd xmm2, xmm0, xmm1
vhaddpd xmm3, xmm2, xmm2
.B1.18:
;;; }
;;;
;;; printf("%f\n", d);
mov DWORD PTR [esp], OFFSET FLAT: ??_C@_03A@?$CFf?6?$AA@
vmovsd QWORD PTR [4+esp], xmm3
vzeroupper
call _printf
アセンブラ中で「v」で始まる命令(リスト中太字の部分)がインテル AVX命令、「ymm0」~「ymm7」はインテル AVXで新たに追加された256ビット長のYMMレジスタである。現在ではAVX対応CPUがないためパフォーマンスは測定できないが、AVX命令を駆使したコードが出力されていることは確認できる。
GCCで出力されたアセンブラコード
Linux/Windows環境で広く使われているコンパイラの1つに、GCCがある。最新版のGCC(GCC 4.5.0)ではSSEサポートが行われており、たとえば「-march=core2 -msse4 -mfpmath=sse」といったオプションを指定することで、SSEを使用するコードを出力できる。たとえば次のリストAは、リスト1のコードを上記のオプション付きでGCCでコンパイルして生成したアセンブラコードである。
このコードではxmm0、xmm1レジスタや「movss」「「mulss」「addss」といったSSE命令が使用されていることが分かる。ただし、こちらもVisual Studioの例と同じくパックド命令は使用されておらず、インテル C++ Composer XE 2011のようなベクトル化は行われていない。
リストA GCCでSSEを使用するよう設定して出力したアセンブラコード
L26: movl $0x00000000, %eax movl %eax, 1560(%esp) movl $0, 1564(%esp) jmp L24 L25: movl 1564(%esp), %eax movss 1044(%esp,%eax,4), %xmm1 movl 1564(%esp), %eax movss 532(%esp,%eax,4), %xmm0 mulss %xmm1, %xmm0 movss 1560(%esp), %xmm1 addss %xmm1, %xmm0 movss %xmm0, 1560(%esp) incl 1564(%esp) L24: cmpl $127, 1564(%esp) setle %al testb %al, %al jne L25 cvtss2sd 1560(%esp), %xmm0 movsd %xmm0, 4(%esp) movl $LC1, (%esp) call _printf incl 1556(%esp) L23: cmpl $999999, 1556(%esp) setle %al testb %al, %al jne L26 movl $0, %eax leave