Hatena::ブログ(Diary)

いたわさににほんしゅ RSSフィード

2011-08-13

gnuplot のテキスト端末出力と、Emacs 連携

ソフトウェアの性能や安定性の懸賞のために、なんらかのテストを行い、結果をグラフ化することがあります。昔からソフトウェア屋に限らず幅広く利用されている gnuplot というグラフの出力ツールがあります。グラフ書く以外にも色々と機能があるみたいなのですが、先日ふと知ったテキスト端末出力が便利だったので、メモがわりのエントリを。

グラフをテキスト端末にアスキーアートで出力すると、チャットやチケットにコピペでき、テスト結果の共有が簡単にできました。最終的には綺麗なグラフを書く必要がある場合も多いでしょうが、速報や要約の目的でケースバイケースで使いわけると便利です。

参考

gnuplot のインストール

私は MacPorts を使っているので port コマンドでインストールしました。

たいていのプラットフォームではパッケージがあることでしょう。

$ sudo port isntall gnuplot

MacPorts では依存で AquaTerm (http://sourceforge.net/projects/aquaterm/) もインストールされます。

後に説明する端末設定で MacOSX では AquaTerm も利用可能です。

gnuplot の実行と、テキスト端末出力

インストールはすぐ終わりましたが、このエントリの主目的であるテキスト端末出力も3行で終わります。

コマンドラインで gnuplot を実行して、2つの gnuplot コマンドを実行しましょう。

$ gnuplot
gnuplot> set terminal dumb
gnuplot> plot sin(x)

はい、終わり。これでお使いの端末にこんなかんじのグラフが出力されたはずです。

    1 ++---------------***---------------+---**-----------+--------**-----++
      +                *  *              +  *  **         +  sin(x) ****** +
  0.8 ++              *   *                 *    *               *    *   ++
      |              *     *               *     *               *     *   |
  0.6 *+             *      *              *     *               *     *  ++
      |*             *      *             *       *             *       *  |
  0.4 +*            *       *             *       *             *       * ++
      |*            *        *            *        *           *        *  |
  0.2 +*           *         *            *        *           *         *++
    0 ++*          *          *          *         *          *          *++
      | *          *          *         *           *         *           *|
 -0.2 ++ *         *          *         *           *         *           *+
      |  *        *           *        *             *        *           *|
 -0.4 ++ *        *            *       *             *       *            *+
      |  *       *              *      *             *      *              *
 -0.6 ++  *      *              *      *             *      *             +*
      |    *    *               *     *               *     *              |
 -0.8 ++   *    *                *   *                 *   *              ++
      +     *  *       +         **  *   +             *  *                +
   -1 ++-----**--------+-----------**----+--------------***---------------++
     -10              -5                 0                5                10

見事なサインカーブ!

説明するまでもないですが、 set terminal コマンドを使って、出力先を dumb に設定しています。この出力先はアスキーアートで出力を表示します。詳しくは日本語の PDF マニュアルを見ると良いでしょう。

最初からテキスト端末を出力先に指定するには、~/.gnuplot を作って、コマンドを実行させます。

set terminal dumb

大きさの設定

set terminal コマンドのオプションで、出力する大きさの設定ができます。

例えば、次のようなコマンドで、チャットに貼りやすそうな、ちょっと小さめに出ます。

set xrange [0:2*pi]
set yrange [-1.4:1.4]
set terminal dumb 50 20   # small
plot sin(x)

表示の際は、軸の刻み(tick)がグラフと重なると見にくいので、ちょっと range を広めに取るのが良さそうです。

結果は次の通り。

      +-----+------+-----+-----+------+-----+-+
      +     +      +     +     +sin(x)+****** |
    1 ++      *****                          ++
      |     **     ***                        |
      |   **         **                       |
  0.5 ++ **            *                     ++
      |**               **                    |
    0 **                 **                  +*
      |                   **                 **
      |                    **               **|
 -0.5 ++                     *            ** ++
      |                       **         **   |
      |                        ***     **     |
   -1 ++                          *****      ++
      +     +      +     +     +      +     + |
      +-----+------+-----+-----+------+-----+-+
      0     1      2     3     4      5     6


Emacs gnuplot-mode.html

メモついでに Emacs の gnuplot インターフェースを。

Emacs のメジャーモードとして gnuplot-mode があり、とても便利です。MacPorts で入れた場合、 /opt/local/share/emacs/site-lisp/gnuplot.el[c] に入ってました。CarbonEmacs の場合は、最初から入ってます。

簡単には、gnuplot のコマンドをファイルに書いておいて、行や範囲指定で実行する、というだけです。それだけでも、 gnuplot はパラメータをちょこちょこ変えながら実行するので、地味に効きます。

ファイルの拡張子は、 *.gp にすると初期設定で有効になるようです。

コマンドは予め定義されている2つと、自分定義の1つ(しか使ってない)。

C-c C-l
ポイントのある行のコマンドを実行する
C-c C-r
選択された範囲のコマンドを実行する
C-c C-p
再描画 (replot)

個人的には同じプロットコマンドで再描画をよく使うので、次のように定義しています。

(defun gnuplot-send-replot-to-gnuplot ()
  "Sends replot to gnuplot"
  (interactive)
  (gnuplot-send-string-to-gnuplot "replot\n" 'line)
  )
(define-key gnuplot-mode-map "\C-c\C-p" 'gnuplot-send-replot-to-gnuplot)

これで、Emacs の中で gnuplot のコマンドを編集して、実行でき、さらにテキスト端末出力の場合は、gnuplot 出力のバッファへグラフも出力されるため、Emacs の中で結果確認までできます。

おまけ AquaTerm (Mac OS X のみ)

アスキーアートではなく、ちゃんとしたグラフ印刷するときには、Mac OS X では AquaTerm が便利なようです。set terminal aqua すると使えます。

以上、gnuplot の小ネタでした。

2011-02-13

basho_bench のインストールと準備の手順紹介

Riak の開発、サポートを行なっている Basho Technologies, Inc. が basho_bench というベンチマークツールを公開しています。

Basho Bench is a benchmarking tool created to conduct accurate and repeatable performance tests and stress tests, and produce performance graphs.

Basho: Benchmarking with Basho Bench

github.com のリポジトリ: https://github.com/basho/basho_bench

Riak 自体のベンチマークに加えて、ドライバを自作すれば簡単にベンチマークを取得することが可能です。

例えば、Hibari のドライバが basho_bench のソースツリーに含まれていますが 300 行程度で書かれています。

利点、特徴をパラパラと。

  • ベンチマーク結果として、スループットとレイテンシのグラフを生成できる
  • 複数の操作を、混在させたベンチマークが取れる
    • 例: READ を 80% + WRITE を 20 %
  • ランダムな値を生成してくれる
    • ランダムなキーと値で WRITE 操作、というのが簡単にかける
  • 1回の試行ごとに、設定ファイルと実行結果を個別ディレクトリに保存してくれる
  • 並列度を指定できる

以下、インストールから、サンプル実行、結果のグラフ生成までの手順メモです。

basho_bench 自体のインストール、ビルド

$ git clone https://github.com/basho/basho_bench.git
$ cd basho_bench
$ make all

この段階で、ベンチマークは実行可能です。basho_bench のサンプル設定を元に、あまり時間がかからない設定にしてみます。

$ cp example/null_test.config .
$ vi null_test.config
{mode, max}.

{duration, 1}.

{concurrent, 1}.

{driver, basho_bench_driver_null}.

{code_paths, ["deps/stats"]}.

{key_generator, {uniform_int, 1024}}.

{value_generator, {fixed_bin, 1024}}.

{operations, [{put, 1}]}.

ベンチマーク自体の実行は、./basho_bench を実行します。

$ ./basho_bench null_test.config
$ ls tests/current           # テスト結果が格納されるディレクトリ

結果グラフを生成してみようとすると...

$ make results
priv/summary.r -i tests/current
env: Rscript: No such file or directory
make: *** [results] Error 127

エラーが発生します。R のインストールと設定を行いましょう。

R のインストール、設定

Mac OS X ではバイナリパッケージがある。楽なのでパッケージを使います。

R for Mac OS X

http://cran.r-project.org/bin/macosx/R-2.12.1.pkg をダウンロード、インストール。

$ make results
priv/summary.r -i tests/current
Warning: unable to access index for repository http://lib.stat.cmu.edu/R/CRAN/bin/macosx/leopard/contrib/2.12
Warning: unable to access index for repository http://lib.stat.cmu.edu/R/CRAN/bin/macosx/leopard/contrib/2.12
Warning messages:
1: In library(Name, character.only = TRUE, logical.return = TRUE) :
  there is no package called 'getopt'
2: In getDependencies(pkgs, dependencies, available, lib) :
  package ‘getopt’ is not available
3: In library(Name, character.only = TRUE, logical.return = TRUE) :
  there is no package called 'ggplot2'
4: In getDependencies(pkgs, dependencies, available, lib) :
  package ‘ggplot2’ is not available
Error: could not find function "getopt"
Execution halted
make: *** [results] Error 1

ライブラリが足りないとのエラーです。R のパッケージをインストールします。

R.app を起動して、[パッケージとデータ] メニューから、パッケージインストーラを選択。

このへん、コマンドラインでどうにかならんのかな。sudo で make results を実行するといけるっぽいけど、調べてない。

一覧を取得から、Japan(Tsukuba)を選択。

getopt, ggplot2, reshape, plyr, proto, digest をインストール。

R.app は終了させてかまいません。

$ make results
$ open tests/current/summary.png

スループットとレイテンシーのグラフが出てきました。

スループットとレイテンシのグラフ サンプル

以上、basho_bench のインストールと準備の手順紹介でした。

-------------

追記

@kenji_rikitake にコマンドラインでもできると教えていただいたので、試してみたメモを追記。

いったんインストールしているパッケージを削除する。念のため、結果グラフ生成でエラーが発生することを確認。

$ R CMD REMOVE getopt ggplot2 reshape plyr proto digest
$ make results   % エラーになる
$ R CMD INSTALL getopt ggplot2 reshape plyr proto digest  % エラーになる

R CMD INSTALL は tar.gz からのインストールらしい。CRAN からのダウンロードは別途必要。

参考: R Installation and Administration http://cran.r-project.org/doc/manuals/R-admin.html


R のシェルからのインストールでは、CRAN からのダウンロードもやってくれるため、そちらでやってみる。

まず、CRAN ミラーの設定。ホームの下に .Rprofile を作って、ミラーを設定する。

参考: CRAN国内ミラーの使い方 - RjpWiki http://www.okada.jp.org/RWiki/?CRAN%B9%F1%C6%E2%A5%DF%A5%E9%A1%BC%A4%CE%BB%C8%A4%A4%CA%FD

$ cat ~/.Rprofile 
options(repos="http://essrc.hyogo-u.ac.jp/cran/") 

R を起動して、パッケージをインストールする。

$ R
> install.packages(c("getopt", "ggplot2", "reshape", "plyr", "proto", "digest"))
> q()   % R シェルを終了させる

2010-12-13

QuickCheck 最初の一歩 リストの逆順を写経

まずは写経。

準備としては、Quviq のサイトから eqc mini を落としてきて適当な場所に解凍するだけです。

http://www.quviq.com/news100621.html

最初の題材は、 Hughes さんのトーク

Functional Programming: A Secret Weapon for Software Testing.

John Hughes gives an invited talk at ICFP 2007 in Freiburg, Germany, showing from his company's experience how QuickCheck has found many obscure bugs in commercial software. *Even when the software under test is not written in a functional language.*

hughes_testing

のなかにある、リストの逆順操作。

単純すぎる例かもしれないけれど、連結操作との交換(といっていいのかな?)で逆順を定義する、というのはとてもシンプル。ちゃんと考えていないけど、これだけで帰納的に操作が定まっているのではないかと思う。

-module(first_samples).

-include_lib("eqc/include/eqc.hrl").

-export([start/0]).

%% 写経
%% http://video.google.com/videoplay?docid=4655369445141008672#

start() ->
  eqc:quickcheck(prop_reverse_combination_mistake()),
  ok.

%% こっちは失敗する。失敗入力がシュリンクされる
prop_reverse_combination_mistake() ->
  ?FORALL({Xs, Ys},
          {list(int()), list(int())},
          lists:reverse(Xs ++ Ys) =:=
            lists:reverse(Xs) ++ lists:reverse(Ys)).

コンパイル。

erlc -I ../eqc-1.0.1/include -pa ../eqc-1.0.1/ebin first_samples.erl

実行

$ erl -pa ../eqc-1.0.1/ebin -pa . -s first_samples -s init stop -noshell
Starting eqc mini version 1.0.1 (compiled at {{2010,6,13},{11,15,30}})
.................Failed! After 18 tests.
{[2,-2],[-5]}
Shrinking....(4 times)
{[0],[1]}

アウチ。こけました。まぁコメントに書いてますね。

前回書いた、QuickCheck の手順を再掲。

  1. 入力をたくさん勝手に生成して、
  2. 何か処理したあとに、
  3. 不変性などのチェックを行う。
  4. そしてチェックが失敗したら、
  5. 入力をなるべく簡略化して失敗する入力を教えてくれる。

まず、実行結果2行目の Failed! までで、勝手に入力を生成してテストをしています。

「何か処理」と「チェック」はコードの prop_reverse_combination_mistake の中にあります。?FORALL マクロの第3引数がそこ。ただし簡単なので「何か処理」ってのは無く、すぐに性質(プロパティ)のチェックを行っています。

そして、18 テストをしたあとに失敗しました、と報告してくれています。その時の入力が

{[2,-2],[-5]}

の部分です。

この後に、入力を簡略化して、なにがおかしいのかを人間に分かりやすくしてくれるプロセスが走ります。 Srinking.. とあるところです。結果として、入力のリストは1要素だけのリストであれば良い、という結果を出力しています。

このケースではそもそも結果が複雑ではありませんが、実際には重要なステップとなるところでしょう。このシュリンクするロジックも気になりますね。どうなってるんだろう。

本来であれば、ここで実装側のバグなのか、性質の定義がおかしいのかを判断するところです。今回は性質の定義が間違っているため、修正して正しい性質にします。

%% こっちは正しい性質
prop_reverse_combination() ->
  ?FORALL({Xs, Ys},
          {list(int()), list(int())},
          lists:reverse(Xs ++ Ys) =:=
            lists:reverse(Ys) ++ lists:reverse(Xs)).

比較の右辺で、入力のリストが逆になっているところがミソですね。

erl -pa ../eqc-1.0.1/ebin -pa . -s first_samples -s init stop -noshell
Starting eqc mini version 1.0.1 (compiled at {{2010,6,13},{11,15,30}})
....................................................................................................
OK, passed 100 tests

テストが通りました。

日本語で書くと、ここで検証した「逆順(L)」という操作の性質(プロパティ)は、次のようなかんじでしょうか。

任意のリスト L に対して、それを任意に分割したサブリスト L = L1 + L2 が

逆順(L) = 逆順(L2) + 逆順(L1)

を満たす。

入力を L1, L2 (Xs, Ys) ではなく、L 自体にしてプロパティを書きなおしてみます。ちょっと煩雑になりますが、このように FORALL マクロを繋げたり、ステートメントをブロック化して、性質の検証前にちょっとした「何か処理」をすることが出来ます。

prop_reverse_combination2() ->
  ?FORALL(L, list(int()),
  ?FORALL(I, choose(0, length(L)),
          begin
            {L1, L2} = lists:split(I, L),
            lists:reverse(L) =:=
              lists:reverse(L2) ++ lists:reverse(L1)
          end)).

list() とか choose() あたりを調べるには、Quviq のトレーニングを受けるのが正当なんでしょうけど、ひとまず次のような調べ方があります。

  1. eqc.hrl の define とか import を見て推測する。
  2. triq ( https://github.com/krestenkrab/triq )の README を読む。
  3. 本家 Haskell の QuickCheck のドキュメントを読んで、Erlang に翻訳する。

まぁどれもいきあたりばったりですね。eqc mini にはドキュメントはほぼありません。

続く、かもしれない。

2010-12-12

QuickCheck 勉強してみよう 最初の一歩の前段階

プログラムを書く際には、ユニットテストも書くでしょう。まぁどっちを先に書くか、という話は置いといて。テストの自動化は品質面でもコスト面でもプラスになるため、できるだけ網羅的にやりたい。しかし網羅は無理なので適当なところで切り上げます。

そこで、前から名前だけは知ってて気になっていた QuickCheck を調べてみようという気になりました。なんと ja.Wikipedia に項目がない、en でもなんかさっぱり短い説明です。Further Reading と External Links が有用そう。まぁ読んでませんが。

QuickCheck - Wikipedia, the free encyclopedia

ざっとまとめるとこんなかんじ。

  • テストケースを「生成」する。
  • テスト対象の関数が持つ「論理的性質」を確認する。
  • Haskell が本家。GHC と HUGS で動く。
  • その他、いろんな言語への移植版有り。

めっちゃ噛み砕くと、

  1. 入力をたくさん勝手に生成して、
  2. 何か処理したあとに、
  3. 不変性などのチェックを行う。
  4. そしてチェックが失敗したら、
  5. 入力をなるべく簡略化して失敗する入力を教えてくれる。

このビデオはモチベーションと基本的なところから入っていてよさげ。PDF 落ちてないのかな。

Functional Programming: A Secret Weapon for Software Testing.

hughes_testing

John Hughes gives an invited talk at ICFP 2007 in Freiburg, Germany, showing from his company's experience how QuickCheck has found many obscure bugs in commercial software.

自分として知りたいこと

自分としては簡略化のところにロジック的な興味をかんじつつも、そこは押さえて実用性の面から調査したいと思います。

で、調査の目的を(すぐ忘れちゃうので)先に書いておく。

テストが可能な範囲
具体的には、いわゆる xUnit 系のユニットテストに対して、どのあたりを QC に任せることができて、どう住み分けるのか。
「論理的性質」のテストってなんなのか
ぱらっと見てて例としてあがるのは、互いに逆の操作があって(逆関数というべき?)、その合成はアイデンティティ( x -> x )である、というもの。この「逆関数パターン」みたいに、他に QC の適用方法のパターン化みたいなのがあるとすごくうれしい。
逆関数パターンの実践
自分が関わっているコードベースで、実際使ってみて有用さを実感できるのか。それともほぼ自明な感覚しか持てないのか。これは対象となる個別の関数の複雑さや実装コードによるので、結論があるものではないけど。
対象
Erlang のプログラム、かつ gen_* のビヘイビアでないような単純なパターンに限る。複雑な FSM への適用にも興味はあるが、手を広げすぎると頭がついていかないので切っとく。

Erlang で使える QuickCheck の実装

ぱらっと探した範囲では、次のような有償、タダ、自由などいくつかの実装がありました。

まずはなんといっても QuickCheck の生みの親、John Hughes さんが作った会社 Quviq の製品。研究とビジネスを両立させているのって偉いな。

QuviQ homepage

名前は QuichCheck そのままみたい。Erlang のモジュール名としては eqc なので、"Erlang QuickCheck" くらいが呼び名かな。

製品と合わせ、Quviq ではトレーニング、コンサルティングも行っている。

Erlang Factory なんかでもプレゼンテーション、トレーニングなどをやっているみたい。例えばこれ。

Erlang Factory 2009 London - John Hughes - QuickCheck en Yahoo! Video

ところで、Quviq はなんと読むのだろうか、キュービック?


次に、同じく、 Quviq の無償版の QuickCheck mini。

2010 年 6 月にリリースしたとのこと []。

a free version of Quviq's product with a selection of features comparable to other free versions such as Haskell QuickCheck. QuickCheck Mini makes Quviq's technology for simplifying failing test cases available to a wide audience, and it can be freely used for testing personal, open source, or commercial software.

http://www.quviq.com/news100621.html

Full バージョンとの違いはここ

Quviq QuickCheck Feature Compari

FSM, PULSE, サポートあたりが違いますね。


Quviq の QC のオープンソースライセンスのクローンを github に 2 つ見つけた。他にもあるのかな。

最初に見つけたのは Triq、「トリック」と呼ぶみたい。ライセンスは Apache License v2 で公開されています。

Triq (pronounced "Trick Check") is a free alternative to QuviQ

eqc. Triq's API is modelled closely after

eqc, so I recommend their tutorials and slides for an introduction

to QuickCheck.

krestenkrab/triq - GitHub

もうひとつは PropEr というプロジェクト。ライセンスは GPLv3 (or lator) で、自由なソフトウェア。

PropEr (PROPerty-based testing tool for ERlang) is a QuickCheck-inspired

open-source property-based testing tool for Erlang

manopapad/proper - GitHub

ドキュメントがきちんと書いてあって素晴らしい。ソースも Triq よりすんなり読めそう。

続きは未定。

2009-11-16 JRuby の並列実行はどう実装されているのか

[ruby][java] JRuby の並列実行でメソッドの追加の実装方法

某所にて、 JRuby のスレッドは本当に並列で走る(global interpreter lock は使っていない)という話を聞きました。Ruby (大文字で始まるので言語のほうね) では、define_method とか動的な仕組みがいろいろとあるので、実装は大変なんじゃなかろうか、と思い、 JRuby のソースを久し振りに眺めてみることにしました。

メソッドの追加と、それと対になるメソッド呼び出し(探索)だけに注目しているので、ほんの一部しか目を通していません。注目するところは、以下の 2 点のみです。

  1. メソッド追加時に取得するロックはなにか
  2. メソッド呼び出し(探索)時にロックを取得するのか、するなら頻度とロックの種類

This is a community wiki dedicated to JRuby, an implementation of the Ruby programming language atop the Java Virtual Machine (JVM).

JRuby: Wiki: Home ― Project Kenai

対象バージョン: 1.4.0

追い掛けやすそうなメソッド追加側

Emacs で rgrep define_method 。RubyModule#define_method が出てきたので、これで間違いなさそう。おおざっぱにまとめると、

  • module 単位で token をもって型を世代管理している (型の名前と token の組で識別)
  • メソッド追加時には、module 単位のロックを取得
  • そのロックを持った中で、
    • メソッドの Map を更新して
    • メソッド探索用のキャッシュを更新する
      • この時は、グローバルなキャッシュを取って
      • 更新で影響を受ける型オブジェクトに対する token を更新している

以下、コード読んだときの走り書き。

RubyModule.java
  L. 1348
    @JRubyMethod(name = "define_method", frame = true, visibility = PRIVATE,
                 reads = VISIBILITY)
    public IRubyObject define_method(ThreadContext context, IRubyObject arg0,
                                     Block block) {
        Ruby runtime = context.getRuntime();
        ...
        RuntimeHelpers.addInstanceMethod(this, name, newMethod,
                           context.getPreviousVisibility(), context, runtime);
  L.  885 addMethod
  L.  895 addMethodInternal
        synchronized(getMethods()) {
            addMethodAtBootTimeOnly(name, method);
            // -> methods#put している
            invalidateCacheDescendants();
            // -> 
            // 1. generation の更新 (token を new Object() で上書き)
            // 2. getRuntime().getHierarchyLock() のロック(グローバル)を持って
            //    includingHierarchy.invalidateCacheDescendants();
        }
  L.  369 getMethods
    methods の getter
    L. 199 private final Map<String, DynamicMethod> methods =
                   new ConcurrentHashMap<String, DynamicMethod>(4, 0.9f, 1);

RuntimeHelpers (org.jruby.javasupport.util)
  L. 1398 addInstanceMethod 
    L. 1403 call addModuleMethod
      L. 1409 addModuleMethod
        L. 1410
          containingClass.getSingletonClass().addMethod(name, new WrapperMethod(
            containingClass.getSingletonClass(), method, Visibility.PUBLIC));
          // containingClass: RubyModule

メソッド呼び出し(探索)側

org.jruby.runtime.Callsite からの型ツリーが呼び出し側っぽい。

ザッとまとめ。

  • 基本はロックを取らない。
  • ときどき(0xFF + 1 = 256)、synchronized をかけてスレッドワーキングメモリを追い出す。
  • token を調べて、古かったら真面目にメソッドを探索する(親なども追い掛ける)。

走り書き。

CashingCallsite.java
  L. 63 call
    L. 64 callAndGetClass
      ThreadContext#callThreadPoll (L.502) // 0xFF 回ごとには synchronized する
    L. 66 CacheEntry#typeOK
      // キャッシュの有効性確認
    L. 69 cacheAndCall
      L. 274 selfType.searchWithCache // selfType: RubyClass
      L. 277 callMethodmissing
      L. 280 method.call  // method: DynamicMethod

RubyModule
  L. 962 searchWithCache
    L. 963 cacheHit // メソッドキャッシュから名前で引いて token を当てる
      取れたらそれを return
    L. 969 取れないときは、階層を上に探索して、キャッシュして返す
      L. 967 - 968 この処理へのコメント
        // we grab serial number first; the worst that will happen is we cache a later
        // update with an earlier serial number, which would just flush anyway
        // 訳: 先にシリアルナンバー(token)を取る。最悪でも、キャッシュされたメソッドに
        //     対する token が古くなるが、それはあとで捨てられるだけである。

実際に動かしてみる

  1. ロック競合がほとんで無いであろう fibonacchi を one thread, two threads で計算
  2. 同じクラスの同じメソッドを define/undefine
  3. 同じクラスの違うメソッドを define/undefine
  4. 違うクラスのメソッドを define/undefine

コードは gist

difine_method_bench.rb

gist: 235787 - GitHub

環境

% sw_vers
ProductName:    Mac OS X
ProductVersion: 10.6.2
BuildVersion:   10C540

% system_profiler SPHardwareDataType | grep Processor
      Processor Name: Intel Core 2 Duo
      Processor Speed: 2.2 GHz
      Number Of Processors: 1

%  ̄/local/jruby-1.4.0/bin/jruby --version
jruby 1.4.0 (ruby 1.8.7 patchlevel 174) (2009-11-02 69fbfa3)
  (Java HotSpot(TM) 64-Bit Server VM 1.6.0_15) [x86_64-java]

結果

2 -> 3 -> 4 の順で実行時間が短くなっていることが分かる。上のソース読みと整合性は取れている。ただし、完全には説明できているわけではない。

%  ̄/local/jruby-1.4.0/bin/jruby define_method_bench.rb 38 2000000
Almost no lock contention (calculate Fibonacchi sequence)
                user     system      total        real
1 thread  (0) 12.088000   0.000000  12.088000 ( 12.038000)
2 threads (0) 14.528000   0.000000  14.528000 ( 14.528000)
1 thread  (1) 12.234000   0.000000  12.234000 ( 12.235000)
2 threads (1) 17.308000   0.000000  17.308000 ( 17.308000)
1 thread  (2) 12.030000   0.000000  12.030000 ( 12.031000)
2 threads (2) 15.870000   0.000000  15.870000 ( 15.870000)
1 thread  (3) 12.298000   0.000000  12.298000 ( 12.298000)
2 threads (3) 15.762000   0.000000  15.762000 ( 15.762000)

Add/remove the SAME method for the SAME class object
                user     system      total        real
1 thread  (0)  4.203000   0.000000   4.203000 (  4.202000)
2 threads (0)  7.575000   0.000000   7.575000 (  7.575000)
1 thread  (1)  3.629000   0.000000   3.629000 (  3.629000)
2 threads (1)  7.204000   0.000000   7.204000 (  7.205000)
1 thread  (2)  3.545000   0.000000   3.545000 (  3.545000)
2 threads (2)  7.292000   0.000000   7.292000 (  7.291000)
1 thread  (3)  3.684000   0.000000   3.684000 (  3.684000)
2 threads (3)  7.060000   0.000000   7.060000 (  7.060000)

Add/remove different methods for the SAME class object
                user     system      total        real
1 thread  (0)  3.758000   0.000000   3.758000 (  3.758000)
2 threads (0)  5.774000   0.000000   5.774000 (  5.774000)
1 thread  (1)  3.840000   0.000000   3.840000 (  3.840000)
2 threads (1)  5.938000   0.000000   5.938000 (  5.938000)
1 thread  (2)  3.709000   0.000000   3.709000 (  3.709000)
2 threads (2)  5.482000   0.000000   5.482000 (  5.483000)
1 thread  (3)  3.749000   0.000000   3.749000 (  3.749000)
2 threads (3)  5.614000   0.000000   5.614000 (  5.613000)

Add/remove different methods for different class objects
                user     system      total        real
1 thread  (0)  3.805000   0.000000   3.805000 (  3.805000)
2 threads (0)  5.156000   0.000000   5.156000 (  5.156000)
1 thread  (1)  3.662000   0.000000   3.662000 (  3.662000)
2 threads (1)  4.900000   0.000000   4.900000 (  4.900000)
1 thread  (2)  3.656000   0.000000   3.656000 (  3.656000)
2 threads (2)  4.611000   0.000000   4.611000 (  4.611000)
1 thread  (3)  3.735000   0.000000   3.735000 (  3.735000)
2 threads (3)  4.800000   0.000000   4.800000 (  4.800000)

補足

Java 仮想マシンの仕様で、 synchronized (修飾子 or ブロック)による、スレッドのワーキングメモリとメインメモリの同期要求に関しては、結城さんの本にに説明があって、すごく分かりやすい(本文じゃなくてコラムかアペンディックスだったかも)。

Amazon.co.jp: 増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編: 結城 浩: 本

ダグ・リー先生のページにしっかりとした説明がある。

In essence, releasing a lock forces a flush of all writes from

working memory employed by the thread, and acquiring a lock

forces a (re)load of the values of accessible fields. While lock

actions provide exclusion only for the operations performed

within a synchronized method or block, these memory effects are

defined to cover all fields used by the thread performing the

action.

Note the double meaning of synchronized: it deals with locks that

permit higher-level synchronization protocols, while at the same

time dealing with the memory system (sometimes via low-level

memory barrier machine instructions) to keep value

representations in synch across threads. This reflects one way in

which concurrent programming bears more similarity to distributed

programming than to sequential programming. The latter sense of

synchronized may be viewed as a mechanism by which a method

running in one thread indicates that it is willing to send and/or

receive changes to variables to and from methods running in other

threads. From this point of view, using locks and passing

messages might be seen merely as syntactic variants of each

other.

Synchronization and the Java Memory Model