未来のいつか/hyoshiokの日記

hyoshiokの日々思うことをあれやこれや

Rubyのキャッシュミスを測定する。

会社のマシンでoprofileを取った。oprofile利用方法(キャッシュミスの測定方法)は会社のブログを参照してほしい。ruby-1.8.5で計測した。

イベントマスクはCPU_CLK_UNHALTEDとDCACHE_PEND_MISSで、前者はクロックごとにイベントが発生し、後者はL1キャッシュミスのイベントである。それぞれ10万回ごとにサンプリングするようにした。

CPU: Core Solo / Duo, speed 2666.77 MHz (estimated)
Counted CPU_CLK_UNHALTED events (Unhalted clock cycles) with a unit mask of 0x00 (Unhalted core cycles) count 100000
Counted DCACHE_PEND_MISS events (Weighted cycles of L1 miss outstanding) with a unit mask of 0x00 (Weighted cycles) count 100000
samples  %        samples  %        app name                 symbol name
132649   10.6887  122439   33.1370  ruby                     os_each_obj
89183     7.1863  1026      0.2777  libcrypto.so.0.9.7a      (no symbols)
60353     4.8632  17284     4.6778  vmlinux                  copy_page_range
57827     4.6596  8810      2.3843  ruby                     rb_eval
53387     4.3019  17372     4.7016  ruby                     gc_mark_children
47991     3.8671  8398      2.2728  ruby                     garbage_collect
43420     3.4987  4320      1.1692  libc-2.3.4.so            memcpy
37168     2.9950  17395     4.7078  ruby                     gc_mark
33726     2.7176  10043     2.7180  ruby                     st_lookup
33245     2.6788  9428      2.5516  vmlinux                  unmap_vmas

ここで、
132649 10.6887 122439 33.1370 ruby os_each_obj
の行の読み方であるが、132649回CPU_CLK_UNHALTEDというイベントをサンプリングして、それは全体の10.6887%であり、DCACHE_PEND_MISSイベントを122439回サンプリングしDCACHE_PEND_MISSイベントのうち33.1370%で、その場所はrubyのos_each_objという関数である。以下同様に見ていく。

libcrypto.so.0.9.7a の中にはシンボルが見あたらないのはデバッグモードでコンパイルされていなかったのであろう。複数のイベントを同時に表示しているので、rubyのos_each_objという関数はクロック毎のサンプル(約10.7%)よりキャッシュミス(33.1%)が多いので、この場所はキャッシュに優しくない場所であるということが分る。逆にlibcrypto.so.0.9.7aの関数は非常にキャッシュに優しい(キャッシュミスが少ない)。

libcだけを見てみると下記のとおり。memcpyはキャッシュミスは少ないが、malloc_consolidateはキャッシュミスが多い。

43420     3.4987  4320      1.1692  libc-2.3.4.so            memcpy
26100     2.1031  6039      1.6344  libc-2.3.4.so            _int_malloc
14779     1.1909  8181      2.2141  libc-2.3.4.so            malloc_consolidate
12857     1.0360  4756      1.2872  libc-2.3.4.so            free
11840     0.9541  3896      1.0544  libc-2.3.4.so            _int_free
5931      0.4779  224       0.0606  libc-2.3.4.so            malloc
2847      0.2294  340       0.0920  libc-2.3.4.so            strcmp
2182      0.1758  245       0.0663  libc-2.3.4.so            strlen
1937      0.1561  15        0.0041  libc-2.3.4.so            msort_with_tmp
1657      0.1335  7         0.0019  libc-2.3.4.so            memchr

os_each_objから呼ばれている下記の/* ここの部分 */でキャッシュミスが多発しているようなので、何か作戦をねればキャッシュミスを削減することはできそうである。

ソースはgc.cの1601行目あたり。

static VALUE
os_obj_of(of)
    VALUE of;
{
    int i;
    int n = 0;

    for (i = 0; i < heaps_used; i++) {
	RVALUE *p, *pend;

	p = heaps[i].slot; pend = p + heaps[i].limit;
	for (;p < pend; p++) {
	    if (p->as.basic.flags) { /* ここの部分 */
		switch (TYPE(p)) {
		  case T_ICLASS:
		  case T_VARMAP:
		  case T_SCOPE:
		  case T_NODE:
		    continue;
		  case T_CLASS:
		    if (FL_TEST(p, FL_SINGLETON)) continue;
		  default:
		    if (!p->as.basic.klass) continue;
		    if (rb_obj_is_kind_of((VALUE)p, of)) {
			rb_yield((VALUE)p);
			n++;
		    }
		}
	    }
	}
    }

    return INT2FIX(n);
}

素人考えだとprefetchだろうなあと思う。