MIRACLE
メールサービス申込 ユーザー登録 パートナー情報
お問い合わせ FAQ サイトマップ
MIRACLE LINUXの特長 製品紹介 サービス案内 購入 サポート 技術フォーラム

プロフィール

吉岡 弘隆 - よしおか ひろたか

日本OSS推進フォーラム ステアリングコミッティ委員
OSDL Board of Directorsを歴任
カーネル読書会主宰

2000年6月、ミラクル・リナックスの創業に参加。
95年~98年、米国OracleにてOracle RDBMSの開発をおこなっていた。
98年にNetscapeのソースコード公開(Mozilla)に衝撃をうけ、オープンソースの世界に飛びこみ、ついには会社も立ち上げてしまう。
2008年6月取締役CTOを退任し一プログラマとなった。

ミラクル関連リンク

なかのひと

サイト検索

2010年8月

1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        

« Rubyのプロファイリング | メイン | テストの参考書 »

コードを読むな、理解しろ

コードを読まないで理解するというと何やら心眼で読めとかテレパシーを使えとか、そーゆー荒唐無稽な方向に走れという事ではなく大局的に理解しましょうという話である。

カーネル読書会のネタで今回はmallocのお話だったのだが、そこでRubyのささださんがいらっしゃっていて、GC(ごみ集め)と記憶域管理の関係について熱い議論が沸騰し、その後いろいろブログなどでフォローされていたりする。

わたしもRubyでmallocやGCがどう実装されているか興味があったのでoprofileで実行プロファイルをとってみたりした。日頃利用しているノートPCでRubyのテストプログラム(test/runner.rb)を実行してoprofileしたのは先日ブログに書いたとおりである。

「それとわたしのノートPCではキャッシュミスを測定できないので、Xeonのマシンでキャッシュミスを測定すると面白いと思った。GCの時ぼろぼろキャッシュミスが発生するということだが、それを巧みに減らすというのが課題になる」ユメのチカラ:Rubyのプロファイリング

思いたったら吉日ということで、会社の最新のDual Coreマシンでキャッシュミスも計測してみた。

そうすると案の定GCでキャッシュミスが多発しているので、そこにちょっとしたパッチをあてればキャッシュミスを減少できるだろうという仮定のもと1行パッチを作成し効果を測定した。キャッシュミスは約半分に減ったのだが実行時間にはほとんど影響がなかった。残念。

さて、oprofileで実行プロファイルを取ってからRubyのパッチを作るまでの道のりであるが、わたし自身はRubyのソースコードにはまったく土地勘がなくほぼ「まるで初めて(略して(まるは)」の初心者が、どのようにパッチを作るかというケーススタディは、初心者ハッカーが自分で獲得しなければいけない道のりという意味でなんかの参考になるのではないかと思い記してみる。

自分のノートにはruby 1.8.4しかはいっていなかったので、最新版の1.8.5をダウンロードする。パッチを作る時、最新版でやるというのは基本中の基本である。(まるはの皆さんここ重要です)

なぜ最新版でやらなければいけないかという理由は101以上あると思うのだが、2〜3あげると、1)最新版ではその問題はすでに解決しているかもしれない、2)コミュニティの力を借りるためには最新版の方が力を借りやすい、3)作成したパッチを本家にとりこんでもらうためには最新版で作成する必要がある、等々。

oprofileを効果的に利用するために、kernel-debuginfoパッケージをあらかじめインストールしておく必要がある。これはカーネルのシンボルを表示するためである。Asianux 2.0 (MIRACLE LINUX 4.0) では kernel-debuginfo-2.6.9-11.19AX というパッケージである。

キャッシュミスイベントを計測するためにインテルのマニュアルを参照する必要がある。Pentium4系のイベントとDual Core系のアーキテクチャでは計測できるハードウェアイベントが異なるので注意が必要である。

今回は下記のようなイベントを設定した。Weighted cycles of L1 miss outstanding というイベントで10万回ごとにサンプリングして、カーネルおよびユーザーモードのイベントを計測する。

opcontrol --event=DCACHE_PEND_MISS:100000:0:1:1

opreport -l
で関数ごとの頻度が表示されるので、最初の2〜3の関数から調査をする。

opreport -dg 詳細が下記のように表示される。

CPU: Core Solo / Duo, speed 2666.77 MHz (estimated)
Counted DCACHE_PEND_MISS events (Weighted cycles of L1 miss outstanding) with
a unit mask of 0x00 (Weighted cycles) count 100000
        vma      samples  %        linenr info                 app name symbol name
000000000042be50 244787   33.2383  gc.c:1661                   ruby         os_each_obj
  000000000042bfac 1        4.1e-04  gc.c:1599
  000000000042bfb9 5         0.0020  gc.c:1599
  000000000042bfbe 6         0.0025  gc.c:1599
  000000000042bfd0 4862      1.9862  gc.c:1601
  000000000042bfd3 228573   93.3763  gc.c:1601
  000000000042bfd6 2698      1.1022  gc.c:1601
  000000000042bfd8 250       0.1021  ruby.h:672
...

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);
}

コードを読まづして、高速道路で一気に目的地に到着した。キャッシュミスが多発している場合の性能向上のパターンとして、1) プリフェッチ、2) カラーリング、3) ブロッキング、等々いろいろある。

プリフェッチというのは、メモリにアクセスするかなり以前にあらかじめフェッチ(アクセス)しておいて、キャッシュに載せておき、実際にアクセスする時点ではキャッシュに載っているのでレイテンシーが低くなる(高速にアクセスできる)というテクニックである。

for ループだと次にアクセスする要素があらかじめわかっているので簡単にプリフェッチのコードを仕込むことができる。コード変更量最小化の法則にしたがうとこれは非常にやりやすい変更である。

ここまではコードを読まづにたどりついたわけだ。パフォーマンスボトルネックという現象を理解するためには、コードを読まづしてツールを利用すればできるという事である。コードの字面だけを追っていてはキャッシュミスや性能上の問題は発見できない。

# diff -u gc.c~ gc.c
--- gc.c~       2006-08-25 17:12:46.000000000 +0900
+++ gc.c        2006-10-03 10:47:08.000000000 +0900
@@ -1594,10 +1594,15 @@
     int n = 0;

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

        p = heaps[i].slot; pend = p + heaps[i].limit;
        for (;p < pend; p++) {
+         if ( (p+1) < pend) {
+               __asm__ __volatile__ (
+                  " prefetch (%0)\n"
+                  : : "r" ((p+1)->as.basic.flags) );
+           }
            if (p->as.basic.flags) {
                switch (TYPE(p)) {
                  case T_ICLASS:

というパッチになった。

コードを読むな、コードを理解しろ

という事である。

参考:

まつもとさんの日記

未来のいつか/hyoshiokの日記 Rubyのキャッシュミスを測定する。

未来のいつか/hyoshiokの日記 Cache Aware Ruby Patch

Rubyソースコード完全解説という名著があるが、その第5章も大変参考になった。

トラックバック

このページのトラックバックURL:
http://www.typepad.jp/t/trackback/4447/6566440

このページへのトラックバック一覧 コードを読むな、理解しろ:

» Oprofile トラックバック * .fridge shell developers blog
いつも参考にさせていただいているこちらの方でOprofileによるrubyのハッ [続きを読む]

» Trick or Treat! 4 トラックバック BLUE NORTH
くー@ くふり(  ̄ー ̄) 899 ハロウィ~ン、パーティっ♪(></~ 和の雰囲気があるお祭りより、サンバとかハロウィンとか、こういうお祭りは好き♪ でも、ちょっと歌舞伎もいいかもと思っている最近...... [続きを読む]

» BINARY HACKS トラックバック ユメのチカラ
高林さんに献本をいただく。本日到着しました。ありがとうございました。 昨年12 [続きを読む]

» RejectKaigi2007 トラックバック ユメのチカラ
RubyKaigi 2007がおわって撤収作業中に、RubyKaigiでreje [続きを読む]

» ソースコードの読み方 トラックバック ユメのチカラ
ソフトウェア工学の標準的なカリキュラムにソースコードの読み方というのがあるのかな [続きを読む]

» チューニング トラックバック ユメのチカラ
パフォーマンスチューニングというのも非常に重要な問題だ。黒魔術のようなかんじもし [続きを読む]

» デバッグ方法論 トラックバック ユメのチカラ
実践的なデバッグ方法論(デバッグの仕方、事例研究)も強く求められている。デバッガ [続きを読む]

» トラブルシューティングのいろは トラックバック ユメのチカラ
トラブルシューティングの第一歩は問題を正しく認識することである。そのために、問題を再現する必要がある。問題を再現するために、どのような条件でその問題が発生しているかの情報を収集しないといけない。 OSのバージョン、ハードウェアの構成からはじまって、問題を発生させているソフトウェア... [続きを読む]

» プログラムの動的で巨視的な理解 トラックバック 未来のいつか/hyoshiokの日記
コードを読むな理解しろ。http://blog.miraclelinux.com/yume/2006/10/post_e3d6.html というわけでもないが、どのようにコードを読むかということはプログラマにとって大変重要な関心事の一つだと思う。誰もが良き読み手になりたいと願うが、誰もそのことについて系統的に... [続きを読む]

コメント

はじめまして,吉岡さま.

プログラムの高速化で
頭を悩ませている点があったので,
今回の記事が大変参考になりました.

これからも記事を楽しみにしています.

K.Tsuchiya

コメントを投稿

会社情報 採用情報 個人情報保護方針 商標等取り扱い事項 English
Copyright(c)2000-2006 MIRACLE LINUX CORPORATION. All Rights Reserved.