Core i7のSSE4.2にも対応、インテル コンパイラーで作るSSE対応プログラム 3ページ

組み込み関数の使用例

 さて、SSE命令では128ビットのレジスタを使用するため、同時に処理できるのは最大128ビットであり、たとえば8ビットサイズの変数であれば最大で16個までである。それ以上の変数に対して処理を行う場合は、次のリスト4のようにループを使ってSSE命令を複数回実行すればよい。

 リスト4は1048576(1024×1024)個の要素を持つint型配列同士の加算を行うコードで、forループを使って実装した場合と、組み込み関数である_mm_add_epi32関数を使って実装した場合とでそれぞれの処理時間を計測するものだ。

リスト4 SSEを使って5個以上の変数の加算処理を行う例

#include "emmintrin.h"
  :
  :
    LARGE_INTEGER freq, begin, end;
    int size = 1024*1024;
    int* a;
    int* b;
    int* result;
    int n;

    a = (int*)_aligned_malloc( sizeof(int) * size, 16 );
    b = (int*)_aligned_malloc( sizeof(int) * size, 16 );
    result = (int*)_aligned_malloc( sizeof(int) * size, 16 );

    srand(11111);
    for( n = 0; n < size; n++ ) {
        a[n] = rand();
        b[n] = rand();
    }

    /* forループを使って実行 */
    QueryPerformanceCounter( &begin );
    for( n = 0 ; n < size; n++ ) {
        result[n] = a[n] + b[n];
    }
    QueryPerformanceCounter( &end );
    printf( "for-loop version: %f sec.\n",  ( (double)(end.QuadPart - begin.QuadPart) / (double)freq.QuadPart ));

    /* SSEを使って実行 */
    QueryPerformanceCounter( &begin );
    for( n = 0; n < size - 3 ; n+= 4 ) {
        *((__m128i*)(result+n)) = _mm_add_epi32( *((__m128i*)(a+n)), *((__m128i*)(b+n)) );
    }
    for( ; n < size; n++ ) {
        result[n] = a[n] + b[n];
    }
    QueryPerformanceCounter( &end );
    printf( "Intrinsic version: %f sec.\n",  ( (double)(end.QuadPart - begin.QuadPart) / (double)freq.QuadPart ));

    _aligned_free(a);
    _aligned_free(b);
    _aligned_free(result);

 なお、16バイト境界に合わせて動的にメモリを確保するには、「_aligned_malloc」関数を用いる。_aligned_mallocは第二引数としてアライメント境界を取る点が異なるだけで、mallocとほぼ同じように利用できる。

    a = (int*)_aligned_malloc( sizeof(int) * size, 16 );

 気になる処理時間の違いであるが、先ほどと同様(表4)の環境でテストを行ったところ、次の表6のような結果となった。この例ではSSEを利用することで、処理時間を約70%程度に短縮できていることが分かる。

表6 整数の繰り返し加算処理にかかった時間(要素数:1048576)
実装 時間(ミリ秒)
SSE(_mm_add_epi32) 3.29
forループ 4.67