Bitmap GCの高速化案

AutorNariさんのBitmap GCの秘蔵パッチを見ていて、
http://www.narihiro.info/resource/patch/gc_diff_bitmap_align_fix.diff

きっと、ここがオーバヘッドだろうと思ったところがあるので、そこの高速化案です。bitmap gcそのものも試していないので、適当です。これまでも私の高速化案が効果があったためしがないので、冗談と思って読んでください。

まな板に上げるのは、

+#define FIND_BITMAP(res, p) do {\
+    if (((RVALUE *)p)->as.free.flags & FL_BMAP_UNDER) {\
+        res = (RVALUE *)((((VALUE)p & BITMAP_MASK) + BITMAP_ALIGN) - (((VALUE)p & BITMAP_MASK) + BITMAP_ALIGN) % sizeof(RVALUE));\
+    }\
+    else {\
+        res = (RVALUE *)(((VALUE)p & BITMAP_MASK) - ((VALUE)p & BITMAP_MASK) % sizeof(RVALUE));\
+    }\
+} while(0)

です。ここで、((VALUE)p & BITMAP_MASK) % sizeof(RVALUE)はいかにも重いなと感じますので、これを何とか速くしようと思いました。

  1. sizeof(RVALUE)は今のRubyの実装では32bit CPUで20になります
  2. (VALUE)p & BITMAP_MASKは必ず4の倍数になります
  3. 1,2から((VALUE)p & BITMAP_MASK) % sizeof(RVALUE)は(((VALUE)p & BITMAP_MASK) % 5) * 4と等価(32bitの場合)です

で、5の余りを求める高速な方法が、「ハッカーのたのしみ」の169ページにあります。ただ、この方法は32bit * 32bitの掛け算を行って、上位ワードを得る必要があります。これを高速に行うにはアセンブラが必要そうです。
無理やり64bit演算を使ってCで書いた余りを求めるプログラムです。

main(int argc, char *argv[]) {
  unsigned long long tmp;
  unsigned long a;
  
  a = atoi(argv[1]);
  printf("SLOW: %d %% 5 = %d\n", a, a % 5);
  
  tmp = a * 0x66666667ULL;
  tmp >>= 33;
  printf("FAST: %d %% 5 = %d\n", a,  a - tmp * 5);
}

この場合では素直に%を使った方がよさそうです。

追記2 (2008/9/1)

    asm("mull %1" : "=d" (__tmp) : "m" (__tmp));\

    asm("mull %1" : "=d" (__tmp) : "g" (__tmp));\

の方がいいと思うので変更しました。"m"と"g"の違いはmはメモリのみですが、gはメモリとレジスタです。__tmpがレジスタに割り当てられたとき、mですといったんメモリに書き出そうとしますが、gですとそのままそのレジスタが指定されます。

追記(2008/8/31)
AutorNariさんにコメントを頂いて、FIND_BITMAPを作ってみました。でも、秘蔵パッチがどうしてもあたらないので、Rubyでの動作チェックが出来ず、簡易化バージョンのチェックのみです。多分、最初のコメントのようにすればいいのではないかなと思います。
このプログラムを作るにあたって、A. SAITOHさんの「GCCインラインアセンブリを使用する方法と留意点等 for x86」(http://www.mars.sannet.ne.jp/sci10/on_gcc_asm.html)が大変役に立ちました。ありがとうございます。

あと、マジックナンバー0x66666667は符号つき用みたいです。符号無しは0xcccccccdを使って右シフトを2回するようです。

/*
#define FIND_BITMAP(res, p) do {\
    VALUE __tmp;\
    if (((RVALUE *)p)->as.free.flags & FL_BMAP_UNDER) {\
      __tmp = (((VALUE)p & BITMAP_MASK) + BITMAP_ALIGN; \
    }\
    else {\
      __tmp = ((VALUE)p & BITMAP_MASK; \
    }\
    asm("movl $0xcccccccd, %eax");\
    asm("mull %1" : "=d" (__tmp) : "g" (__tmp));\
    __tmp >>= 4;\
    res = (RVALUE *)(__tmp * 20);\
} while(0)
*/


typedef unsigned long VALUE;
typedef unsigned long RVALUE;

#define FIND_BITMAP(res, p) do {\
    VALUE tmp;\
    tmp = (VALUE)(p);				\
    asm("movl $0xcccccccd, %eax");\
    asm("mull %1" : "=d" (tmp) : "g" (tmp));\
    tmp >>= 4;\
    res = (RVALUE *)(tmp * 20);\
} while(0)

main(){
  RVALUE *b;
  RVALUE *a;
  a = (RVALUE *)0x3208;
  FIND_BITMAP(b, a);
  printf("%d %d", a, b);
}

これで、割り算よりスピードアップしたか調べればいいのですが、もう寝る時間ですので寝ます。おやすみなさい。