HPC/並列プログラミングポータルでは、HPC(High Performance Computing)プログラミングや並列プログラミングに関する情報を集積・発信しています。 |
[記事一覧を見る]
インテル コンパイラーの特徴の1つに「自動ベクトル化」がある。これはSSE(Streaming SIMD Extensions)と呼ばれるCPUの機能を利用することで数値演算の高速化を図るものだ。インテル コンパイラーは最新のCore i7といったCPUに搭載されているSSE4.2に対応しており、現行のほとんどのインテルCPUにおいて高速化が期待できる。本記事では、インテル コンパイラーでのSSEの使用と、その効果について解説する。
インテル コンパイラーには生成するプログラムの処理速度を向上させるためのさまざまな機能が備えられているが、その1つに「自動ベクトル化(Auto vectorization)」というものがある。これは、forループなど同一の演算を繰り返すような処理を、インテル製のCPUが持つ「SSE(Streaming SIMD Extensions)」という命令群を利用して複数のデータを一括処理することで高速化を図る機能だ。
SSEはPentium III以降のインテル製CPUが備えている機能で、専用128ビットレジスタを使用し、複数の演算処理を一括して実行する、というものだ。この専用レジスタは32ビット環境では8本、64ビット環境では16本が利用できる。SSEを利用することで、たとえば32ビット環境では32ビットのデータを4つ、16ビットのデータなら8つを一括して処理できるようになり、処理時間の高速化が期待できる(図1)。
SSEのリリース当初は70個の命令が含まれていたが、続いて2000年には新たに114個の命令を追加したSSE2が登場、以後もSSE3、SSSE3(Supplemental Streaming SIMD Extensions 3)、SSE4.1、SSE4.2と改良版がリリースされている。各CPUが対応SSEバージョンをまとめたものが表1だ。
SSE命令バージョン | CPU |
---|---|
SSE4.2 | Core i7、Xeon 55XXシリーズ |
SSE4.1 | Xeon 74XX、54XX、52XX、33XX、31XXシリーズ、Core 2 Extreme 9XXXシリーズ、Core 2 Quad 9XXXシリーズ、Core 2 Duo 8XXXシリーズおよびE7200 |
SSSE3(Supplemental Streaming SIMD Extensions 3) | Xeon 73XX、72XX、53XX、51XX、32XX、30XXシリーズ、Core 2 Extreme 7XXX、6XXXシリーズ、Core 2 Quad 6XXXシリーズ、Core 2 Duo 7XXX(E7200を除く)、6XXX、5XXX、4XXXシリーズ、Core 2 Solo 2XXXシリーズ、Pentium dual-core E2XXX、T23XXシリーズ、Atomシリーズ |
SSE3 | Xeon 70XX、71XX、50XXシリーズ、ULV/LV版Xeon1.66、2.0、2.16、Xeon 2.8、Core Duo、Core Solo、Pentium dual-core T21XX、T20XXシリーズ、Pentium Extreme Edition、Pentium D |
SSE2 | 上記以外のPentium 4、Xeon |
SSEには上位互換性があり、たとえばSSE4.2に対応したCPUであれば、SSSE3やSSE3、SSE2、SSEに含まれる命令すべてを利用できる。それぞれに含まれる命令の詳細についてはインテルのWebページ等を参照してほしいが、基本的な整数および単精度/倍精度浮動小数点の計算についてはSSE2でほぼカバーされており、SSE3およびSSSE3、SSE4.1、SSE4.2ではメモリからのロードや単純でない演算処理などを実行するための命令が追加されている。
なお、インテルはSSEの後継として「Intel AVX(Advanced Vector Extensions)」という技術の開発を行っている。AVXは256ビットレジスタを利用し、これによってより多数の演算を同時実行できるようになるとのことだ。AVXを搭載したCPUは2010年以降にリリースされる見込みで、まだ市場には出回っていないものの、インテル コンパイラー 11.1ではいち早くAVXのサポートが行われている。
SSEは「マルチメディア処理向け命令」をうたっていたMMXの後継という側面もあるものの、その用途はマルチメディア処理だけにとどまらず、すべての数値演算処理や文字列処理などにも及ぶ。そのため、多くのアプリケーションでSSEによるパフォーマンスの向上が期待できる。
SSEを利用するには、インラインアセンブラを利用してSSE命令をソースコード中に直接記述するという方法がある。しかし、インラインアセンブラの利用にハードルの高さを感じる人も多いだろう。そこで以下では、アセンブラを利用せずに、C/C++中でSSEによるプログラムの高速化を行う方法について述べていこう。
まず、もっとも手軽なのがコンパイラの最適化機能を利用する方法だ。インテル コンパイラーでは、特にソースコードを変更することなしにSSEを利用するコードを自動的に出力できる。たとえばインテル コンパイラー 11.1ではデフォルトでSSE2を使用したコードを生成するようになっている。また、「/Qx<使用するSSEバージョン>」(Windows版)もしくは「-x<使用するSSEバージョン>」(LinuxおよびMac OS X版)コンパイルオプションの設定によってCPUを指定し、SSE3以降の命令を利用することも可能だ(表2)。
コンパイルオプション | 最適化対象CPU | |
---|---|---|
Windows版 | Linux | |
/QxHost | -xHost | コンパイルを実行したPCのCPU |
/QxAVX | -xAVX | Intel Advanced Vector Extentions(AVX)をサポートするCPU |
/QxSSE4.1 | -xSSE4.1 | SSE 4.1をサポートするCPU |
/QxSSE4.2 | -xSSE4.2 | SSE 4.2をサポートするCPU |
/QxSSSE3 | -xSSSE3 | SSSE3をサポートするCPU |
/QxSSE3_ATOM | -xSSE3_ATOM | Atomシリーズ |
/QxSSE3 | -xSSE3 | SSE3をサポートするCPU |
/QxSSE2 | -xSSE2 | SSE2をサポートするCPU |
なお、AtomにはSSE3に加えてバイトオーダー変換付きのロード/ストアを高速に行う「MOVBE」命令が追加されており、「/QxSSE3_ATOM」や「-xSSE3_ATOM」とともに「/Qinstruction:movbe」(Windows版)もしくは「-minstruction=movbe」(Linux版)というコンパイルオプションを指定することで、この命令を利用するコードを生成できる。
ちなみに、この「/Qx」もしくは「-x」オプション付きでコンパイルしたプログラムは、最適化対象として指定したCPU以外では実行できない。たとえば「/QxSSSE3」オプション付きでコンパイルしたプログラムをSSE3をサポートしないPentium 4やPentium Mを搭載したPC上で実行しようとすると、ランタイムエラーが発生する。もし特定のCPU以外でも動作するプログラムを作成したい場合は、「/Qax<使用するSSEバージョン>」(Windows版)もしくは「-ax<使用するSSEバージョン>」(LinuxおよびMac OS X版)というオプションを利用する(表3)。これらのオプションを利用すると、使用するSSEバージョンに応じた複数のコードが生成され、実行時にランタイムライブラリによって、実行するCPUに最適なコードが選択・実行される。ただし、このオプションを指定することで若干のオーバーヘッドが発生するほか、バイナリサイズが大きくなるので注意が必要である。
コンパイルオプション | 対応するSSEバージョン | |
---|---|---|
Windows版 | Linux | |
/QaxSSE4.2 | -axSSE4.2 | SSE4.2、SSE4.1、SSSE3、SSE2、SSE2、SSE |
/QaxSSE4.1 | -axSSE4.1 | SSE4.1、SSSE3、SSE3、SSE2、SSE |
/QaxSSSE3 | -axSSSE3 | SSSE3、SSE3、SSE2、SSE |
/QaxSSE3_ATOM | -axSSE3_ATOM | Atom |
/QaxSSE3 | -axSSE3 | SSE3、SSE2、SSE |
/QaxSSE2 | -axSSE2 | SSE2、SSE |
さて、それでは簡単なサンプルコードで、インテル コンパイラーによる自動ベクトル化の効果を確認してみよう。サンプルに使用したのは、次のリスト1のようなコードである。
void VectorizationTest() { int size = 100*1024*1024; int* i; float* f; double* d; int max_i; float max_f; double max_d; LARGE_INTEGER freq, begin, end; int n; i = (int*)_aligned_malloc( sizeof(int) * size, 16 ); f = (float*)_aligned_malloc( sizeof(float) * size, 16 ); d = (double*)_aligned_malloc( sizeof(double) * size, 16 ); srand(111); for( n = 0; n size; n++ ) { i[n] = rand(); f[n] = (float)rand() / (float)RAND_MAX; d[n] = (double)rand() / (double)RAND_MAX; } QueryPerformanceFrequency( freq ); /* int */ QueryPerformanceCounter( begin ); max_i = i[0]; for( n = 0; n size; n++ ) { if( max_i i[n] ) { max_i = i[n]; } } QueryPerformanceCounter( end ); printf( "int: %f sec.\n", ( (double)(end.QuadPart - begin.QuadPart) / (double)freq.QuadPart )); /* double */ QueryPerformanceCounter( begin ); max_d = d[0]; for( n = 0; n size; n++ ) { if( max_d d[n] ) { max_d = d[n]; } } QueryPerformanceCounter( end ); printf( "double: %f sec.\n", ( (double)(end.QuadPart - begin.QuadPart) / (double)freq.QuadPart )); /* float */ QueryPerformanceCounter( begin ); max_f = f[0]; for( n = 0; n size; n++ ) { if( max_f f[n] ) { max_f = f[n]; } } QueryPerformanceCounter( end ); printf( "float: %f sec.\n", ( (double)(end.QuadPart - begin.QuadPart) / (double)freq.QuadPart )); printf( "max: %d.\n", max_i ); printf( "max: %lf.\n", max_d ); printf( "max: %lf.\n", max_f ); }
このコードは、100×1024×1024個の要素を持つint、float、double型配列に格納されている数値の中で最大となるものを探索し、それぞれの場合で探索にかかった時間を計測するものだ。
このコードを「/QxSSSE3」(SSSE3対応CPU向け)、「/QxSSE4.2」(SSE4.2対応CPU向け)、指定無しという3種類のコンパイルオプションでコンパイルし、実行した結果が次の表5である。なお、テストに利用したのは表4のような環境である。float型およびdouble型の場合はどの場合もほとんど処理時間に変化は無かったが、int型の場合は/QxSSE4.2オプション付きでコンパイルすることで、5%程度の高速化が実現できている。
要素 | スペック |
---|---|
CPU | Core i7 920(2.66GHz) |
メモリ | 3GB |
OS | Windows Vista Home Premium(32bit版) |
開発環境 | Visual Studio 2008、インテル コンパイラー 11.1 |
型 | 実行時間(秒) | ||
---|---|---|---|
指定無し | /QxSSSE3 | /QxSSE4.2 | |
int | 0.0530 | 0.0533 | 0.0503 |
float | 0.0546 | 0.539 | 0.0536 |
double | 0.107 | 0.107 | 0.106 |
この処理時間の違いであるが、SSE4で新たに追加された、整数型の最大値を探索する「PMAXSD」という命令がその要因である。これは複数のint型変数の最大値を求める命令で、/QxSSE4.2オプション付きでコンパイルしたコードではこの命令が使用され処理の高速化が図られている。/QxSSSE3オプション付きでコンパイルした実行ファイルと、/QxSSE4.2オプション付きでコンパイルした実行ファイルについて、int型配列の探索を行っている部分のアセンブラコードを抜き出したものが次のリスト2およびリスト3だ。/QxSSSE3オプション付きの場合は「PCMPGTD」という、MMXに含まれる命令を使用して比較を行っているのに対し、/QxSSE4.2オプション付きの場合はPMAXSD命令を使用しており、アセンブラコードの行数も短くなっているのが確認できる。
004010DB: 8B 94 24 94 00 00 mov edx,dword ptr [esp+94h] 00 004010E2: 8B 3A mov edi,dword ptr [edx] 004010E4: 83 E2 0F and edx,0Fh 004010E7: 74 35 je 0040111E 004010E9: F6 C2 03 test dl,3 004010EC: 0F 85 C5 03 00 00 jne 004014B7 004010F2: 89 B4 24 90 00 00 mov dword ptr [esp+90h],esi 00 004010F9: 8B B4 24 94 00 00 mov esi,dword ptr [esp+94h] 00 00401100: F7 DA neg edx 00401102: 83 C2 10 add edx,10h 00401105: C1 EA 02 shr edx,2 00401108: 33 C0 xor eax,eax 0040110A: 8B 0C 86 mov ecx,dword ptr [esi+eax*4] 0040110D: 3B CF cmp ecx,edi 0040110F: 0F 4D F9 cmovge edi,ecx 00401112: 40 inc eax 00401113: 3B C2 cmp eax,edx 00401115: 72 F3 jb 0040110A 00401117: 8B B4 24 90 00 00 mov esi,dword ptr [esp+90h] 00 0040111E: 8B 8C 24 94 00 00 mov ecx,dword ptr [esp+94h] 00 00401125: 8B C2 mov eax,edx 00401127: F7 D8 neg eax 00401129: 83 E0 03 and eax,3 0040112C: F7 D8 neg eax 0040112E: 66 0F 6E C7 movd xmm0,edi 00401132: 66 0F 70 C0 00 pshufd xmm0,xmm0,0 00401137: 05 00 00 40 06 add eax,6400000h 0040113C: 66 0F 6F 0C 91 movdqa xmm1,xmmword ptr [ecx+edx*4] 00401141: 66 0F 6F D1 movdqa xmm2,xmm1 00401145: 66 0F EF C8 pxor xmm1,xmm0 00401149: 83 C2 04 add edx,4 0040114C: 66 0F 66 D0 pcmpgtd xmm2,xmm0 00401150: 66 0F DB D1 pand xmm2,xmm1 00401154: 66 0F EF C2 pxor xmm0,xmm2 00401158: 3B D0 cmp edx,eax 0040115A: 72 E0 jb 0040113C 0040115C: 66 0F 6F C8 movdqa xmm1,xmm0 00401160: 66 0F 6F D0 movdqa xmm2,xmm0 00401164: 66 0F 73 D9 08 psrldq xmm1,8 00401169: 66 0F EF C1 pxor xmm0,xmm1 0040116D: 66 0F 66 D1 pcmpgtd xmm2,xmm1 00401171: 66 0F DB D0 pand xmm2,xmm0 00401175: 66 0F EF D1 pxor xmm2,xmm1 00401179: 66 0F 6F C2 movdqa xmm0,xmm2 0040117D: 66 0F 6F DA movdqa xmm3,xmm2 00401181: 66 0F 73 D8 04 psrldq xmm0,4 00401186: 66 0F EF D0 pxor xmm2,xmm0 0040118A: 66 0F 66 D8 pcmpgtd xmm3,xmm0 0040118E: 66 0F DB DA pand xmm3,xmm2 00401192: 66 0F EF D8 pxor xmm3,xmm0 00401196: 66 0F 7E DF movd edi,xmm3 0040119A: 3D 00 00 40 06 cmp eax,6400000h 0040119F: 73 17 jae 004011B8 004011A1: 8B 8C 24 94 00 00 mov ecx,dword ptr [esp+94h] 00 004011A8: 8B 14 81 mov edx,dword ptr [ecx+eax*4] 004011AB: 3B D7 cmp edx,edi 004011AD: 0F 4D FA cmovge edi,edx 004011B0: 40 inc eax 004011B1: 3D 00 00 40 06 cmp eax,6400000h 004011B6: 72 F0 jb 004011A8
004010DB: 8B 37 mov esi,dword ptr [edi] 004010DD: 8B C7 mov eax,edi 004010DF: 83 E0 0F and eax,0Fh 004010E2: 74 1F je 00401103 004010E4: A8 03 test al,3 004010E6: 0F 85 7E 03 00 00 jne 0040146A 004010EC: F7 D8 neg eax 004010EE: 83 C0 10 add eax,10h 004010F1: C1 E8 02 shr eax,2 004010F4: 33 D2 xor edx,edx 004010F6: 8B 0C 97 mov ecx,dword ptr [edi+edx*4] 004010F9: 3B CE cmp ecx,esi 004010FB: 0F 4D F1 cmovge esi,ecx 004010FE: 42 inc edx 004010FF: 3B D0 cmp edx,eax 00401101: 72 F3 jb 004010F6 00401103: 8B D0 mov edx,eax 00401105: F7 DA neg edx 00401107: 83 E2 03 and edx,3 0040110A: 66 0F 6E C6 movd xmm0,esi 0040110E: 66 0F 70 C0 00 pshufd xmm0,xmm0,0 00401113: F7 DA neg edx 00401115: 81 C2 00 00 40 06 add edx,6400000h 0040111B: 66 0F 6F 0C 87 movdqa xmm1,xmmword ptr [edi+eax*4] 00401120: 66 0F 6F D0 movdqa xmm2,xmm0 00401124: 66 0F 6F C1 movdqa xmm0,xmm1 00401128: 66 0F 38 3D C2 pmaxsd xmm0,xmm2 0040112D: 83 C0 04 add eax,4 00401130: 3B C2 cmp eax,edx 00401132: 72 E7 jb 0040111B 00401134: 66 0F 70 C8 0E pshufd xmm1,xmm0,0Eh 00401139: 66 0F 38 3D C1 pmaxsd xmm0,xmm1 0040113E: 66 0F 70 D0 39 pshufd xmm2,xmm0,39h 00401143: 66 0F 38 3D C2 pmaxsd xmm0,xmm2 00401148: 66 0F 7E C6 movd esi,xmm0 0040114C: 81 FA 00 00 40 06 cmp edx,6400000h 00401152: 73 11 jae 00401165 00401154: 8B 04 97 mov eax,dword ptr [edi+edx*4] 00401157: 3B C6 cmp eax,esi 00401159: 0F 4D F0 cmovge esi,eax 0040115C: 42 inc edx 0040115D: 81 FA 00 00 40 06 cmp edx,6400000h 00401163: 72 EF jb 00401154
インテル コンパイラーでSSEを利用するもう1つの手段として、組み込み関数(Intrinsics)と呼ばれている関数群を利用する方法がある。インテル コンパイラーのドキュメントでは、「組み込み関数はアセンブラで記述された関数であり、C++の関数内で呼び出せるほか、(C/C++の)変数を適切にアセンブラ命令に渡すことができる」とされている。
この説明では若干分かりにくいが、要は組み込み関数はCPU命令をC/C++の関数として呼ぶためのラッパー関数である。CPU命令をC/C++で利用する方法としては他にインラインアセンブラがあるが、組み込み関数はCの関数呼び出しと同様の形式でコードを記述できるため、メンテナンス性が高いのが特徴だ。組み込み関数はコンパイル時にインライン関数として展開されるため、呼び出しのオーバーヘッドも少ない。
インテル コンパイラーにはMMXおよびSSE2~SSE4.2、Intel AVXに含まれる各命令に対応した組み込み関数が用意されており、それぞれに対応したヘッダーファイルをincludeすることで利用可能になる。たとえばSSE2に含まれる加算命令「PADDD」は、組み込み関数では次のように定義されている。
__m128i _mm_add_epi32(__m128i a, __m128i b)
詳細についてはインテル コンパイラーのドキュメントなどを参照してほしいが、これは4個の32ビット整数同士を加算するものだ。使用例は次のようになる。
_declspec(align(16)) int a[4]; _declspec(align(16)) int b[4]; _declspec(align(16)) int result[4]; /* ここでa、bに値を代入 */ a[0] = ... : : /* 加算の実行 */ *((__m128i*)result) = _mm_add_epi32( *((__m128i*)a), *((__m128i*)b) );
なお、MMX/SSE命令では処理対象となるメモリが16バイト境界に合わせて確保されていないと一般保護例外が発生する場合がある。メモリを16バイト境界に合わせて確保するには、変数を宣言する個所に「_declspec(align(16))」を付加すればよい。
さて、SSE命令では128ビットのレジスタを使用するため、同時に処理できるのは最大128ビットであり、たとえば8ビットサイズの変数であれば最大で16個までである。それ以上の変数に対して処理を行う場合は、次のリスト4のようにループを使ってSSE命令を複数回実行すればよい。
リスト4は1048576(1024×1024)個の要素を持つint型配列同士の加算を行うコードで、forループを使って実装した場合と、組み込み関数である_mm_add_epi32関数を使って実装した場合とでそれぞれの処理時間を計測するものだ。
#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%程度に短縮できていることが分かる。
実装 | 時間(ミリ秒) |
---|---|
SSE(_mm_add_epi32) | 3.29 |
forループ | 4.67 |
インテル コンパイラーではabsやsin、cos、tan、logといった各種数値演算関数や、strcpy、strlen、memcpy、memsetといった文字列/メモリアクセス関数といった、C標準ライブラリ関数の一部についても組み込み関数として用意されている。これらの組み込み関数については、「/Oi」(Windows版)もしくは「-fbuiltin」(Linux、Mac OS X版)オプション付きでコンパイルを行うことで利用できる。
たとえばC標準ライブラリ関数には切り上げ/切り捨て処理を行う関数としてceilおよびfloorという関数が用意されているのだが、インテル コンパイラーで組み込み関数を有効にし、かつSSE4向けの最適化を行うように設定すると、これらの関数がSSE4で追加された浮動小数点の丸め演算命令(ROUND*命令)を利用するものに置き換わり、高速に実行できるようになる。
次のリスト5のようなコードをコンパイルオプションを変えてコンパイルし、その実行速度の違いを検証したところ、表7のような結果となった(テスト環境は先ほどと同様、表4のとおり)。組み込み関数を使用してSSE4.1および4.2向けの最適化を行った場合、floorおよびceilの実行時間がそれぞれ半分程度にまで短縮されていることが分かる。
SSEの指定 | 実行時間(秒) | |
---|---|---|
floor | ceil | |
指定なし | 0.262 | 0.172 |
SSE2 | 0.260 | 0.173 |
SSE3 | 0.258 | 0.171 |
SSSE3 | 0.258 | 0.174 |
SSE4.1 | 0.156 | 0.072 |
SSE4.2 | 0.156 | 0.072 |
float* A; float* B; int n; int dim = 1024*1024*100; LARGE_INTEGER freq, begin, end; srand(1111); for( n = 0; n dim; n++ ) { A[n] = (float)rand() / (float)(RAND_MAX); } QueryPerformanceFrequency( freq ); QueryPerformanceCounter( begin ); for( i = 0; i dim; i++ ) { B[i] = ceil(A[i]); } QueryPerformanceCounter( end ); printf( "ceil: %f sec.\n", ( (double)(end.QuadPart - begin.QuadPart) / (double)freq.QuadPart )); QueryPerformanceFrequency( freq ); QueryPerformanceCounter( begin ); for( i = 0; i dim; i++ ) { B[i] = floor(A[i]); } QueryPerformanceCounter( end ); printf( "floor: %f sec.\n", ( (double)(end.QuadPart - begin.QuadPart) / (double)freq.QuadPart ));
そのほか、インテル コンパイラー 11.1に付属するライブラリ「インテル インテグレーテッド・パフォーマンス・プリミティブ(IPP)」についても、AtomおよびCore i7対応が行われている。といっても、プログラムのコンパイル時やリンク時に特に意識する必要はなく、リンクしたプログラムの実行時にCPUが判別され、対応する関数やコードが自動的に実行される。また、IPPのランタイムライブラリDLLについても、x86汎用のものに加えて特定のCPU向けに最適化されたものが用意されている。
たとえば、IPPのサンプルとしてインテルが提供している、H.264エンコーダ(「umc_h264_dec_con.exe」)で比較してみると、SSEを利用しないx86汎用のライブラリを使用した場合と、SSE4向けのライブラリを使用した場合とで10倍以上パフォーマンスが異なるという結果となった(表8)。テストに使用した環境は先ほどと同様、表4のとおりである。
使用するライブラリ | エンコード時間(秒) |
---|---|
汎用版 | 73 |
SSE4向け最適化版 | 6.73 |
このサンプルプログラムは米インテルのWebサイトからダウンロードできるもので、詳細については 『マルチメディア処理から信号処理まで幅広く活用できる高速ライブラリ「IPP」』でも解説しているのでそちらを確認してほしい。なお、比較に使用した動画ファイルはWindows Vistaに含まれるサンプルビデオ「湖.wmv」(縦横サイズは720×480、長さは約16秒間)をYUV形式に変換したものを用いている。
SSEが初めて搭載されたPentium IIIが登場した1999年から約10年が経過した現在では、ほぼすべてのPCでSSEの利用が可能と言っても過言ではない。SSEを利用することで、多くの状況でパフォーマンスの向上が期待できるため、パフォーマンスが必要とされるアプリケーションではSSEの積極利用を検討するべきであろう。
ただし、自動ベクトル化は万能ではなく、コードによってはうまく最適化が行われない場合もある。その場合、インテル コンパイラーの「/Qvec-report」(Windows版)もしくは「-vec-report」(LinuxおよびMac OS X版)オプションを利用することで、ベクトル化できない個所やその依存性などに関するレポートを確認できる。これらによって問題点を特定し、ソースを変更して原因を排除できれば自動ベクトル化が可能になる場合もある。自動ベクトル化と組み込み関数の使用を組み合わせ、より細かい最適化を行っていくと良いだろう。
[ページ情報]
更新日時: 2009-11-16 18:29:43, 更新者: hiromichi-m
[権限]
表示:無制限, 編集:ログインユーザ, 削除/設定:メンバー