C99に対応した標準Cライブラリの実装レポートを行っていきます。

プロフィール 

Author:高木信尚

ホームページ
ブログ

最近の記事 

最近のコメント 

最近のトラックバック 

月別アーカイブ 

カテゴリー 

ブロとも申請フォーム 

この人とブロともになる

ホーム 全記事一覧 << 前の記事 次の記事 >>

 

2008/10/07 11:41|

 

前回のstrxfrm関数で使ってしまった関係上、今回は急遽memcpy関数を取り上げることにしました。こういう単純関数ほど、工夫する価値が結構あったりします。今回もアセンブリ言語を使う一歩手前まで最適化してみたいと思います。

この類の関数は、ループの中をどれだけ軽量化できるか、あるいはループの回数をどれだけ減らせるかが鍵になります。まずは素直な実装からです。

#include <string.h>

void *memcpy(void * __restrict__ s1, const void * __restrict__ s2, size_t n)
{
  register char *ss1;
  register const char *ss2;

  for (ss1 = s1, ss2 = s2; n != 0; n--)
    *ss1++ = *ss2++;
  return s1;
}

やっていることはご覧の通りですので、特に説明もいらないでしょう。コンパイル結果を見てみることにしましょう。オプションは、-mh -mint32 -O2 -fomit-frame-pointerです。

_memcpy:
    mov.l   er4,@-er7
    mov.l   er0,er4
    mov.l   er2,er0
    mov.l   er4,er3
.L8:
    mov.l   er0,er0
    beq .L7
    mov.b   @er1+,r2l
    mov.b   r2l,@er3
    adds    #1,er3
    subs    #1,er0
    bra .L8
.L7:
    mov.l   er4,er0
    mov.l   @er7+,er4
    rts

例によって、H8/300Hのコードであることにご注意ください。上記の.L8から.L7までがループになっています。er3がss1、er1がss2、er0がnに相当するようです。beq .L7はforの条件式の判定です。

これを踏まえた上で、もう一歩踏み込んでみます。

void *memcpy(void * __restrict__ s1, const void * __restrict__ s2, size_t n)
{
  register char *ss1 = s1;
  register const char *ss2 = s2;
  register const char *t = ss2 + n;

  while (ss2 != t)
    *ss1++ = *ss2++;
  return s1;
}

今度は、nをデクリメントするのではなく、終端位置を指すポインタを予め作っておいて、その値との比較を行うようにしました。同じ条件でコンパイルすると、次のようになります。

_memcpy:
    mov.l   er4,@-er7
    mov.l   er0,er4
    mov.l   er0,er3
    mov.l   er1,er0
    add.l   er2,er0
.L7:
    cmp.l   er0,er1
    beq .L6
    mov.b   @er1+,r2l
    mov.b   r2l,@er3
    adds    #1,er3
    bra .L7
.L6:
    mov.l   er4,er0
    mov.l   @er7+,er4
    rts

大体同じですが、subs #1,er0の分だけ、ループの中が1行減っています。たった1命令ですが、ループの回数が増えれば、その数だけ命令数が減少することになります。さらにもう一歩踏み込みます。

void *memcpy(void * __restrict__ s1, const void * __restrict__ s2, size_t n)
{
  register char *ss1 = s1;
  register const char *ss2 = s2;

  if (n != 0)
  {
    register const char *t = ss2 + n;
    do
      *ss1++ = *ss2++;
    while (ss2 != t);
  }
  return s1;
}

今度はdo文を使いました。今までの二つは、ループの最初で条件判定を行っていたので、どうしてもループの中にbeq命令が入っていました。これをループの最後に判定させることで、ループの最後のbra命令をbeq命令とまとめてしまおうというのが狙いです。それでは同じ条件でのコンパイル結果です。

_memcpy:
    mov.l   er4,@-er7
    mov.l   er0,er4
    mov.l   er0,er3
    mov.l   er2,er2
    beq .L2
    mov.l   er1,er0
    add.l   er2,er0
.L3:
    mov.b   @er1+,r2l
    mov.b   r2l,@er3
    adds    #1,er3
    cmp.l   er0,er1
    bne .L3
.L2:
    mov.l   er4,er0
    mov.l   @er7+,er4
    rts

狙い通り、ループの中はうんとシンプルになりました。

ここから先の最適化は、これまでバイト単位で書き込んでいたものをワード単位で書き込むようにすることになると思います。しかし、その方法を採用するとなると、H8/300と、H8/300HやH8Sでは別のコードを書かなければ真の最適化ができなくなります。

また、ワード単位の書き込みの場合、最初に境界調整を調べたりする処理が入るので、サイズが小さい場合はかえって効率が低下します。この辺りの落としどころも難しいので、今回はこの程度にしておきたいと思います。

アセンブリ言語を使う場合は、H8にはEEPROM命令というブロック転送の命令があるので、それを使うのがよさそうです。この話は、いずれ機会を改めてできたらと思います。
2006/03/15 02:27|文字列操作TB:0CM:0

コメント
コメントの投稿

管理者にだけ表示を許可する


トラックバック
トラックバックURLはこちら
http://libc.blog47.fc2.com/tb.php/37-37e20156

ホーム 全記事一覧 << 前の記事 次の記事 >>

ブログ内検索 

お勧め書籍 

RSSフィード 

リンク 

このブログをリンクに追加する

Powered By FC2ブログ 

Powered By FC2ブログ
ブログやるならFC2ブログ

Copyright(C) 2006 TAKAGI Nobuhisa All rights reserved.
Powered by FC2ブログ. 無料ホームページ アフィリエイト レンタルサーバー FC2ブログ 一戸建て template designed by 遥かなるわらしべ長者への挑戦.