その続きの話になりますが、ご意見頂けると嬉しいです。よろしくお願いします。
環境: Core Duo T2300 (1.66GHz), メモリ2GB, WinXP Home SP3 (32bit), VC2008EE, C言語, Win32API
SIMD命令を使うと実際にどれくらい速くなるのか?試したところ、期待したほど速くありませんでした。
いろいろ試したところ、どうも処理するメモリサイズが小さいうちは効果が大きく、
メモリサイズが大きいと効果が薄れる?ように見えます。
これはどういうことか?また処理メモリサイズが大きくても効果を出すテクニックはあるのか?
というあたりで、詳しい方のご意見を聞きたくて質問させて頂きました。
具体的には、32bitビットマップ画像をメモリにロードして加工処理することを想定したテストプログラムを作りました。
「連続したメモリ領域を、単純に先頭から順次更新する処理を、何回も繰り返す」コマンドラインプログラムです。
処理するメモリサイズをだんだん大きくしていき、SIMD命令を使った場合と使わない場合の速度を比べます。
以下プログラムです。
#pragma comment(lib, "winmm.lib")
#include <windows.h>
#include <mmsystem.h>
#include <emmintrin.h>
#include <stdio.h>
// メモリサイズ。ビットマップ画像サイズに相当。
#define ALLOC_BYTE_BEGIN (512 * 512 * 4) // 開始
#define ALLOC_BYTE_END (1024 * 1024 * 4) // 終了
#define ALLOC_BYTE_UNIT (1024 * 64) // 増加単位バイト
// ループ回数。結果ハッキリさせるため適当に調節。
#define LOOP_COUNT_BEGIN 3000 // 開始
#define LOOP_COUNT_UNIT 100 // 減らす単位
#define LOOP_COUNT_MIN 500 // 最小
void SIMD( __m128i* p, UINT bytes )
{
__m128i* end = (__m128i*)((LPBYTE)p + bytes);
__m128i i255 = _mm_set1_epi8( 255 );
for( ; p<end; p++ ) // 先頭から128bit(16byte)ずつ
{
*p = _mm_xor_si128( *p, i255 );
}
}
void Normal( LPBYTE p, UINT bytes )
{
LPBYTE end = p + bytes;
for( ; p<end; p+=16 ) // 先頭から4ピクセル(16byte)ずつ
{
p[0] = ~p[0]; // B
p[1] = ~p[1]; // G
p[2] = ~p[2]; // R
p[3] = ~p[3]; // A
p[4] = ~p[4]; // B
p[5] = ~p[5]; // G
p[6] = ~p[6]; // R
p[7] = ~p[7]; // A
p[8] = ~p[8]; // B
p[9] = ~p[9]; // G
p[10] = ~p[10]; // R
p[11] = ~p[11]; // A
p[12] = ~p[12]; // B
p[13] = ~p[13]; // G
p[14] = ~p[14]; // R
p[15] = ~p[15]; // A
}
}
int main( void )
{
UINT bytes, loop=LOOP_COUNT_BEGIN;
for( bytes=ALLOC_BYTE_BEGIN; bytes<=ALLOC_BYTE_END; bytes+=ALLOC_BYTE_UNIT )
{
DWORD t1, t2;
LPVOID p;
UINT n;
p = VirtualAlloc( NULL, bytes, MEM_COMMIT, PAGE_READWRITE );
if( !p ) return -1;
VirtualLock( p, bytes );
memset( p, 0xaa, bytes );
t1 = timeGetTime();
for( n=loop; n>0; n-- ) Normal( p, bytes );
t1 = timeGetTime() - t1;
t2 = timeGetTime();
for( n=loop; n>0; n-- ) SIMD( p, bytes );
t2 = timeGetTime() - t2;
VirtualUnlock( p, bytes );
VirtualFree( p, 0, MEM_RELEASE );
printf("%u KB (%u回), Normal=%u.%usec, SIMD=%u.%usec, %0.1f倍!!\n",
bytes/1024, loop, t1/1000, t1%1000, t2/1000, t2%1000, (float)t1/t2);
loop -= LOOP_COUNT_UNIT;
if( loop<LOOP_COUNT_MIN ) loop=LOOP_COUNT_MIN;
}
return 0;
}
2MBを超えたあたりから効果が薄れはじめ、最後4MBでは1.5倍くらいしか速くなっていません。
1024 KB (3000回), Normal=2.688sec, SIMD=0.562sec, 4.8倍!!
1088 KB (2900回), Normal=2.703sec, SIMD=0.563sec, 4.8倍!!
:
2048 KB (1400回), Normal=2.702sec, SIMD=0.703sec, 3.8倍!!
2112 KB (1300回), Normal=2.422sec, SIMD=0.671sec, 3.6倍!!
2176 KB (1200回), Normal=2.282sec, SIMD=0.781sec, 2.9倍!!
:
4032 KB (500回), Normal=1.812sec, SIMD=1.219sec, 1.5倍!!
4096 KB (500回), Normal=1.828sec, SIMD=1.250sec, 1.5倍!!
このように、処理メモリサイズが大きくなるとSIMD命令の効果が薄くなる理由として、
自分としては以下のように考えていますが、正しいでしょうか?
・処理メモリサイズが小さいうちは、CPUのキャッシュ(1次キャッシュ、2次キャッシュ)に乗るため速い。
・CPUキャッシュに乗らないサイズになると、メインメモリのアクセスが必要になり遅くなる。
また、上のプログラムのような、単純な1ループ、単純なシーケンシャルアクセス、単純なSIMD命令1つでは、
これ以上速くするのは難しいのかな・・と思ってしまいますが、テクニックがあったりするのでしょうか・・?
SIMD命令に「プリフェッチ命令」というのがあり、効果的な使い方がよくわからず適当に入れてみたことは
あるのですが、逆に遅くなってしまったような気がします。。
アセンブラコードを書かないのに生意気なことを言っているかもしれません・・ご容赦ください・・。
なお、マルチスレッドはとりあえず考えていません。マルチスレッド化したらどうなるか?も興味はありますが・・。
ひとまず「SIMD命令でどこまで速くなるのかな?」という点に絞っています。
よろしくお願いします。