HPC/並列プログラミングポータルでは、HPC(High Performance Computing)プログラミングや並列プログラミングに関する情報を集積・発信しています。

新着トピックス

 さて、ここでは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