いきなりですが ARM アセンブラの話でも。
memcpy の亜種(戻り値なし、必ずコピー発生)を C 言語で、ARM が得意なコード サイズ重視の書き方で書くと、
void my_memcpy(void* dst, const void* src, size_t size) { uint8_t* dstC; const uint8_t* srcC; assert(size != 0); /* 必ずコピーが発生するとして */ dstC = dst, srcC = src; do { *dstC++ = *srcC++; } while (--size != 0); }
こんな感じになります。
これを ARM アセンブラに置き換えると、
my_memcpy PROC loopBegin LDRB r3,[r1],#1 STRB r3,[r0],#1 SUBS r2,r2,#1 BNE loopBegin BX lr ENDP
こんな感じになります。(アセンブラ手書きだけれど間違ってないかな
たった 5 命令(LDRB, STRB, SUBS, BNE, BX)で memcpy の亜種が実装できます。ARM アセンブラを初めて見たときは感動したものです。ARM は C コードをアセンブラで短く収めることに関しては他のプロセッサの追随を許しませんね。
―――あとはプロセッサ自体が速ければ…。
補足
ARM の場合、命令列が短いことと高速に動作するコードであることはほぼ同義ですが、分岐命令とメモリ アクセスが含まれる場合はこの限りではないです。それぞれ対処法は下記のとおり。
- 分岐命令への対処
- 命令順序を変更して条件フィールドの活用で分岐命令を削減する
- 長いループの終了判定の場合ループ展開する
- 上限の分かっている短いループの終了判定の場合ジャンプ テーブルに展開する
- メモリ アクセスへの対処
- メモリ アクセス回数を削減する(基本)
- 可能な限り多重レジスタ アクセスでメモリ アクセスする
結構泥臭いことばかりですが、これらを気をつけることで、ARM の実行効率は驚くほど向上します。みんな!ARM の底力を引き出していこう!!