32ビットのmemset
memsetはメモリの初期化によく使うけど
8ビット単位でしか処理できないよね?
たとえば3D計算とかしてると 1.0f ( 0x3f800000 )
で初期化したいって時に forループで設定してしまうよね?
いい関数知ってる人いたら教えて下さい。。。
ぱっと考えて大きなデータをforループで設定するのって重そう
なので、 32ビット単位で memsetできる関数考えてみよう
とりあえず MMX使わずに DWORDで処理
movsd 使えばいいんだ。
引数は 初期化する要素数、destアドレス、設定するDWORD値
愚直に forループ
void floop( int*p, int num, int nCnt )
{
for( int n=0; n<nCnt; n++ )
{
p[n] = num;
}
}
64ビットアセンブラにまず限定して
_asm_memset32a PROC _size:DWORD, _dest:DWORD, _dataa:DWORD
mov rbx, rdi
; 引数設定
mov rdi, rdx
mov rax, r8
rep stosd
; epilogue
mov rdi, rbx
mov rax, rdx
ret
_asm_memset32a endp
アラインを調整してみる。ちょっとベタ書きで
_asm_memset32a PROC _size:DWORD, _dest:DWORD, _dataa:DWORD
mov rbx, rdi
; 引数設定
mov rdi, rdx
mov rax, r8
mov r9, r8
; Dword Align チェック
and rdx,11b
; 最初のアラインずれ分
CopyDword:
; jmp qword ptr CopyLeadByte[0+rdx*8]; あれ? この書き方x64じゃ出来ないの?
lea r8, CopyLeadByte
jmp qword ptr [rdx*8 + r8];
align 8
CopyLeadByte dq CopyLeadByte0, CopyLeadByte3, CopyLeadByte2, CopyLeadByte1
align 8
CopyLeadByte0:
; Align OK
rep stosd
mov rdi, rbx
mov rax, r9
ret
align 8
CopyLeadByte3:
; lead
stosb
ror eax, 8
stosb
ror eax, 8
stosb
ror eax, 8
dec rcx
rep stosd
; trail
stosb
ror eax, 8
; end
mov rdi, rbx
mov rax, r9
ret
align 8
CopyLeadByte2:
; lead
stosb
ror eax, 8
stosb
ror eax, 8
dec rcx
rep stosd
; trail
stosb
ror eax, 8
stosb
ror eax, 8
; end
mov rdi, rbx
mov rax, r9
ret
align 8
CopyLeadByte1:
; lead
stosb
ror eax, 8
dec rcx
rep stosd
; trail
stosb
ror eax, 8
stosb
ror eax, 8
stosb
ror eax, 8
; end
mov rdi, rbx
mov rax, r9
ret
_asm_memset32 endp
で、それぞれを パフォーマンス測定
1024個のfloatを アライン0、アライン1 で1000000回ループした時間測定
#define NUM 1024
char *pa, *pa0, *pa1;
pa = (char*)malloc( sizeof(float)*10000 );
pa0 = (char*) ((int)(pa+3) & 0xFFFFFFFFFFFFFFFC); // アライメント0
pa1 = pa+1; // アライメント1
float f=1.0f;
int dat = *((int*)&f); // 1.0f の値
int *pi0 = (int*)pa0;
int *pi1 = (int*)pa1;
PERFORMANCE_COUNT( "loop_a0", floop( pi0, dat, NUM ), 1000000 );
PERFORMANCE_COUNT( "loop_a1", floop( pi1, dat, NUM ), 1000000 );
PERFORMANCE_COUNT( "memset32a_a0", memset32a( pa0, dat, NUM ), 1000000 );
PERFORMANCE_COUNT( "memset32a_a1", memset32a( pa1, dat, NUM ), 1000000 );
PERFORMANCE_COUNT( "memset32_a0", memset32( pa0, dat, NUM ), 1000000 );
PERFORMANCE_COUNT( "memset32_a1", memset32( pa1, dat, NUM ), 1000000 );
free( pa );
PERFORMANCE_COUNT は マクロで、関数を指定した回数回し、デバッグウインドウに時間を表示する
結果 まずDebugビルド
loop_a0:: loop[1000000]: Time[2552ms]: FPS[391849.529781]
loop_a1:: loop[1000000]: Time[2517ms]: FPS[397298.371077]
memset32a_a0:: loop[1000000]: Time[93ms]: FPS[10752688.172043]
memset32a_a1:: loop[1000000]: Time[127ms]: FPS[7874015.748031]
memset32_a0:: loop[1000000]: Time[97ms]: FPS[10309278.350515]
memset32_a1:: loop[1000000]: Time[135ms]: FPS[7407407.407407]
上から
forループ アライン0
forループ アライン1
アセンブラ(アライン揃えなし) アライン0
アセンブラ(アライン揃えなし) アライン1
アセンブラ(アライン揃えあり) アライン0
アセンブラ(アライン揃えあり) アライン1
アセンブラコードがいい加減だからか、 アライン揃えしない方が速い・・・
でもコピーするデータ量が多くなれば アラインした方が良い
だけど forループより25倍ぐらい速い。これは良さそう
そして Releaseビルド
見せてもらおうか Microsoftのコード最適化の性能のやらを!
loop_a0:: loop[1000000]: Time[89ms]: FPS[11235955.056180]
loop_a1:: loop[1000000]: Time[126ms]: FPS[7936507.936508]
memset32a_a0:: loop[1000000]: Time[91ms]: FPS[10989010.989011]
memset32a_a1:: loop[1000000]: Time[126ms]: FPS[7936507.936508]
memset32_a0:: loop[1000000]: Time[92ms]: FPS[10869565.217391]
memset32_a1:: loop[1000000]: Time[130ms]: FPS[7692307.692308]
ほぼ同じ。若干負け。
もちろん 今回はループ回数が固定値できりのよい数値だったり
単純なコードなので 最適化がよく効いたので
アライン揃えを行わない movsd 使ったアセンブラの価値はありそう
だけど なんでこんなに速いのか 気になるよね
Align0
000000013F66D5FF movsxd rax,dword ptr [rsp+38h]
000000013F66D604 movsxd rdi,ecx
000000013F66D607 mov ecx,400h
000000013F66D60C and rdi,0FFFFFFFFFFFFFFFCh
000000013F66D610 rep stos dword ptr [rdi]
Align1
000000013FC1D5EB mov ecx,400h
000000013FC1D5F0 lea rdi,[rax+1]
000000013FC1D5F4 mov r11,rax
000000013FC1D5F7 movss xmm0,dword ptr [__real@3f800000 (13FC52D3Ch)]
000000013FC1D5FF movss dword ptr [rsp+38h],xmm0
000000013FC1D605 movsxd rax,dword ptr [rsp+38h]
000000013FC1D60A rep stos dword ptr [rdi]
stosd で送ってるじゃん・・・ アライン無視で。
インライン展開された分&レジスタの最適化やパイプライン最適化が出来るぶん
コンパイラ最適化の勝ちね・・・・・
結論からいうと、forループでもたいていの場合最適化されるので 32ビットのmemset関数は不要
ただし、複雑な条件だと最適化されないと思うので 作っていて損は無い
でも コンパイラの最適化に勝ちにいくには、MMX系のインストラクションを使うべき
前から知ってたけど、コンパイラの最適化技術って すごいわ