memset関数は、メモリブロック中の全バイトを指定した文字で埋めます。単純な関数ですが、結構奥が深く、最適化の余地もかなりあります。ところが、いろいろ実験してみたところ、C言語レベルでの最適化は限界があり、あまりよい結果が得られません。
memset関数の最適化は、メモリブロックへの書き込みを、可能な限りワード単位で行うようにするものですが、memset関数の使用頻度が最も高いのは、構造体のゼロ初期化である可能性が高く、それほど大きなメモリブロックを扱わないのであれば、いっそバイト単位で操作した方が、無駄が少なく、効率が上がる可能性もあります。
とりあえず、ワード単位で書き込むコードを挙げます。
#include <stddef.h> #include <stdint.h>
void *memset(void *s, int c, size_t n) { if (n != 0) { register uintptr_t addr = (uintptr_t)s; register uintptr_t t = addr + n; if (((addr | n) & 1) == 0) { for (unsigned int val = (uint8_t)c | (uint8_t)c << 8; addr < t; addr += sizeof(uint16_t)) { *(uint16_t*)addr = val; } } for (; addr < t; ++addr) *(uint8_t*)addr = c; } return s; }
上のコードを-mh -mint32 -O2 -fomit-frame-pointerオプションを指定してコンパイルすると、次のようになります。
_memset: mov.l er4,@-er7 mov.l er5,@-er7 mov.l er6,@-er7 mov.l er0,er4 mov.l er1,er5 mov.l er2,er2 beq .L2 mov.l er0,er3 add.l er2,er0 or.l er4,er2 btst #0,r2l bne .L3 sub.l er2,er2 mov.b r1l,r2l mov.l er2,er1 mov.w e1,r6 mov.b r6l,r6h mov.b r1h,r6l mov.b r1l,r1h sub.b r1l,r1l mov.w r6,e1 or.l er2,er1 cmp.l er0,er4 bhs .L3 .L7: mov.w r1,@er3 adds #2,er3 cmp.l er0,er3 blo .L7 .L3: cmp.l er0,er3 bhs .L2 mov.b r5l,@er3 adds #1,er3 bra .L3 .L2: mov.l er4,er0 mov.l @er7+,er6 mov.l @er7+,er5 mov.l @er7+,er4 rts
内部的に使用しているレジスタが多く、そのためにer4, er5, er6の3つのレジスタを退避しなければならず、あまり好ましい結果になりませんでした。
現状では、メモリブロックへの書き込みを順方向(アドレスの小さい方から大きい方)に行っていますが、これを逆方向に行うようにすれば、変数 t が不要になるため、若干なしになる気もしますが、とりあえず、現時点ではこのままにしておき、アセンブリ言語で記述しなおす際に再検討したいと思います。
|