ゆき社長

シーゲンガーのお勉強 ゲームプログラマ、ゲーマー、色々!

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系のインストラクションを使うべき

前から知ってたけど、コンパイラの最適化技術って すごいわ