ReflectionClassでメソッドを動的に呼ぶ&ベンチマーク

以前に変数を使って、クラスに対して動的にプロパティを追加してみた。
Objectに動的にプロパティを追加する - うっかりプログラミング日誌

今度は動的にメソッドを呼んでみる。*1

<?php
class Test1 {
  public function hoge() {
    return 1 + 1;
  }
}

$test = new Test1();
$method = 'hoge';

$test->$method(); //=> 2

簡単!

ReflectionClassを使う

とはいえ、この方法だと引数が固定数しか取れない。
そこでReflectionMethod#invokeArgsを使う。

<?php
class Test2 {
  public function hoge($a, $b) {
    return $a + $b;
  }
}

$test = new Test2();
$method = 'hoge';
$args = array(1, 3);

$ref = new ReflectionClass(get_class($test));
$met = $ref->getMethod($method); // ReflectionMethodクラスが返される
$met->invokeArgs($test, $args); //=> 4

このReflectionClassが結構便利。
複数のクラスを受け取れるメソッドを用意しておいて、
ReflectionClass#hasMethod()と組み合わせたりすると良い。
ちなみにReflectionMethod#invoke()だと上記の変数呼び出しと同じ使い方になる。

で、遅くないの?

実はここが本題。
動的にメソッドを呼び出すのはいいけど、一体どっちが早いのか?
メソッド呼び出しを100万回くらいやれば速度差が出るかと思い、やってみた。
PEAR::Benchmarkを使っています。

<?php
class Test1 {
  public function hoge() {
    return 1 + 1;
  }
}


require_once('Benchmark/Timer.php');
$timer = new Benchmark_Timer();

$test = new Test1();
$method = 'hoge';
$max = 1000000;

$timer->start();

// 変数呼び出し
for($i=0;$i < $max; $i++) {
  $test->$method();
}
$timer->setMarker('Variable');

// ReflectionClass
for($i=0;$i < $max; $i++) {
  $ref = new ReflectionClass(get_class($test));
  if ($ref->hasMethod($method)) $met = $ref->getMethod($method);
  $met->invoke($test);
}
$timer->setMarker('Reflection');

$timer->stop();
$timer->display();

/*
result
---------------------------------------------------------
marker       time index            ex time         perct   
---------------------------------------------------------
Start        1251959593.35938300   -                0.00%
---------------------------------------------------------
Variable     1251959594.44166300   1.082280         5.83%
---------------------------------------------------------
Reflection   1251959611.91642100   17.474758       94.17%
---------------------------------------------------------
Stop         1251959611.91648500   0.000064         0.00%
---------------------------------------------------------
total        -                     18.557102      100.00%
---------------------------------------------------------
*/

newを含めると、なんと17倍の差が。
純粋にinvokeとの速度差を調べてみました。

<?php
class Test1 {
  public function hoge() {
    return 1 + 1;
  }
}


require_once('Benchmark/Timer.php');
$timer = new Benchmark_Timer();

$test = new Test1();
$method = 'hoge';
$max = 1000000;

$timer->start();

for($i=0;$i < $max; $i++) {
  $test->$method();
}
$timer->setMarker('Variable');

$ref = new ReflectionClass(get_class($test));
if ($ref->hasMethod($method)) $met = $ref->getMethod($method);

for($i=0;$i < $max; $i++) {
  $met->invoke($test);
}
$timer->setMarker('Reflection');

$timer->stop();
$timer->display();

/*
---------------------------------------------------------
marker       time index            ex time         perct   
---------------------------------------------------------
Start        1251959636.48438300   -                0.00%
---------------------------------------------------------
Variable     1251959637.56774800   1.083365        33.69%
---------------------------------------------------------
Reflection   1251959639.69969100   2.131943        66.30%
---------------------------------------------------------
Stop         1251959639.69976100   0.000070         0.00%
---------------------------------------------------------
total        -                     3.215378       100.00%
---------------------------------------------------------
*/

それでもReflectionの方が倍くらいの時間がかかる。

  • クラスやメソッドを解析して、厳密にやるならReflection。
  • チェックとか特に必要なく呼び出せるなら変数。

という感じで使い分けると良さそう。

*1:これの呼び方がよくわからない

array_mapでstaticメソッドを呼び出す時に、selfが使えない

ここのところPHPばかりやっているので、小ネタ。


PHP 5.2.8 for Windows での話。

array_mapで関数名を指定する時、通常は文字列で指定する。

<?php
function _add3($a) {
  return $a + 3;
}
$array = array(1,2,3);
print_r(array_map('_add3', $array));

クラス内の関数の場合はarrayで指定する。

<?php
class Test {
  public function _add3($a) {
    return $a + 3;
  }
  public function hoge($array) {
    return array_map(array($this, '_add3'), $array);
  }
}
$test = new Test();
$array = array(1,2,3);
print_r($test->hoge($array));

インスタンスメソッドが$thisで指定できるなら、staticメソッドはselfでいける!と思ったら・・・

<?php
class Test {
  public static function _add3($a) {
    return $a + 3;
  }
  public function hoge($array) {
    return array_map(array(self, '_add3'), $array);
  }
}
$test = new Test();
$array = array(1,2,3);
print_r($test->hoge($array));

なぜかselfが定義されていない定数というNoticeが出る。

Notice: Use of undefined constant self - assumed 'self' in test.php on line 6

s/self/get_class($this)/
s/self/__CLASS__/

でNoticeは出なくなる。selfの実体は'self'という文字列なので、クラス名の文字列を渡す必要があるコールバック関数では使えないってことかしら。
まあ、E_STRICTを切ればいいんだけど・・・ねえ。

参考

静的なクラスメソッドの場合、 0 番目の要素としてオブジェクトを渡す代わりにクラス名を渡すことにより、 オブジェクトのインスタンスを作成せずに渡すことができます。

PythonのDecorator

きっかけはwycatsのこの記事

Python Decorators in Ruby « Katz Got Your Tongue?

RubyPythonのDecoratorを実装してみたよー」ってことらしいが、Pythonなんてprintしか知らない僕にはDecoratorがなんだかわからない。
元が何者だかわかっていないので、Rubyでの実装コードを見てみても、いまいち掴めない。


というわけで、手を動かしてPython書いてみた。
下記ページのサンプルを動かしてみた程度。

Python decorator

python のデコレーターは closure に syntax sugar を かぶせたものだと言われます。

とのことです。
元の関数をすり替えてしまえるし、Closureの引数として元の関数自体も呼び出せる。
パッと使い道は出てこないけど、便利そうな感じがしますね!


ここまでやったところで、1年前にRubyで実装されていたのを見つけた。

http://d.hatena.ne.jp/technohippy/20080723#1216808401
http://yugui.jp/articles/792

とりあえず、Decoratorがちょっとわかった。
あとは自分で実装してみる!・・・かどうかはわからない。

Python3.0で変わった事2つ

Decoratorのサンプルを書いている時に、Python2.6と 3.1 の両方でやってみた。
その時にはまった変更点を2つメモ。

  • print が構文から関数に変更されている
#python 3.1
print 'hoge' #=> Error!
print('hoge') #=> hoge
  • func_X の関数が __X__に変更されている

unc_Xとは関数オブジェクトのinspectを行う関数の集まり。

func_Xと名づけられていた関数の属性は、__X__というフォームに改称されました。古いfunc_X形式の名前は関数の属性の名前空間は、ユーザが定義する属性(名)として解放されました。即ちfunc_closure、func_code、func_defaults、func_dict、func_doc、func_globals、func_nameはそれぞれ__closure__、__code__、__defaults__、__dict__、__doc__、__globals__、__name__に改称されました。

だそうな。


Python 3.0 での変更点全訳はこちら
http://text.world.coocan.jp/TSNET/?What%27sNewInPython3.0

複数のRubyを切り替えられるようにする

Ruby1.9をメインで使いつつ、1.8系も準備しておきたかったので併存させられる環境を作ってみた。


OS: Ubuntu 9.04 Server Edition

Rubyのソースを用意する

Subversionで落としてくる。当然trunk。安定とか知りません。

$ svn co http://svn.ruby-lang.org/repos/ruby/trunk ruby
$ svn co http://svn.ruby-lang.org/repos/ruby/branches/ruby_1_8 ruby1.8
必要なライブラリを入れる

OpenSSLとconfigure作成用のautoconf
bisonがどうとかいうエラーが出るので、それも入れておく

$ sudo apt-get install libssl-dev
$ sudo apt-get install autoconf
$ sudo apt-get install bison
makeする

/local/ruby の下にバージョン番号のディレクトリを作成して、そこに入れる。
プログラムにはバージョン番号を付けておく

$ cd ruby
$ autoconf
$ ./configure --prefix=/local/ruby/<VERSION> --program-suffix=<VERSION> --with-readline-dir=/local/ruby/<VERSION>
$ make
$ make test
$ sudo make install
シンボリックリンクを張る

aliasを付けてもいいけど、gemとか他のプログラムが /usr/bin/ruby とかを見ている場合があるので、シンボリックリンクを作っておく。
投げやりなシェルスクリプトにしてみる。

$ less switch_ruby.sh
#!/bin/sh
ORG_PATH=/local/ruby/$2/bin
PREFIX=/usr/local/bin

for NAME in ruby gem irb rdoc ri 
do
  ln -s $ORG_PATH/$NAME$1 $PREFIX/$NAME
  echo "/usr/bin/$NAME linked @ $ORG_PATH/$NAME$1"
done

$ ./switch_ruby.sh 1.9 1.9.1
併存させたいバージョンの数だけ繰り返す

1.8系はrubygemsのビルドもあるので注意。今回はめんどくさいのでやってない。


バージョンを切り替えたい場合はシンボリックリンクを入れ替えればよし。
これでtrunk Rubyライフを満喫!!

Mitaka.rb #3 & Tokyu.rb #9 に行ってきた

連日だったので、まとめて。
小田急線沿いというどっちつかずの中途半端な位置に生息しているため、
両方に参加してきました。

以下、飲み食いしすぎで記憶がうつろですが覚えている限り。

Mitaka.rb #3

  • 会場: 三鷹のリトルスターレストラン
  • 30人以上でざわざわ。プログラマーズカフェの人々も多かった。
  • 料理はとてもとてもうまかった。食い過ぎで次の日腹痛かったのは内緒。
  • 5人ほどLT! After Ruby Kaigi!
  • Hamlの会会長、Director's cutで再び。削ったスライドからの質問が飛んだりとかw
  • btoさんの話は初めて聞いたことなので大変面白かった。

Tokyu.rb #9

  • 出席率高い!宴会席が満席に。
  • 大企業についての色々を聞いたり、RDBやkey-value, BigTableについてのよもやま。
  • 今のところは用途や特化すべきものによって使い分け。今後は?
  • Rails会議の話をしている横で組み込みクラスタが出来ていたりとカオス。
  • 会話がかなーりの濃度で、酔ってる暇なかった。
  • でも肉は食い過ぎて腹が(ry
  • 7人くらいで2次会へ。
  • サロンパスさんとひたすらbot談義。結構楽しそうな世界だ。
  • ウイスキー飲んでたら酔ってた。


どっち付かずな生息地なのに、参加させてくれてありがとうございます。
皆様お疲れ様でした!

日本Ruby会議2009に行ってきた

今年初参加です。
3日分の感想等まとめてアップ。それなりに長いけど中身は大してありません。

1日目

Scott Chacon from Github
  • 英語が早くてほとんど聴き取れず。スライドがわかりやすかった
  • gitの使い方や概念、git の運用パターンなど
  • 特にgitの運用パターンは参考になりそう。あとで穴が空く程動画見る。
  • Grit ruby-git library http://github.com/mojombo/grit/tree/master
  • Gitのホスト設定が超めんどくさいので自動化した。
現場で役立つRails (大場さん@万葉)
  • Railsの実装パターンや陥りやすい罠に対する対策などを解説
  • 見事にダメパターンな実装をしていたことがわかり、一人で凹んだw
Adherison (Jason Goecke)
  • 後ろの方で充電ついでにコード書いてたので、あんまり聞いてませんでしたごめんなさい。
  • でも、Ruby による自動応答アプリケーションのデモはすごかった。
  • http://adherison.com
Rails3 (Yahuda Kats@EngineYard)
  • 英語が堪能すぎて全く聴き取れませんでした。
  • スライドはとてもよかったので、もう一度見直す
  • とりあえず「Rails3はすげー変わっちゃうよ」ってことは把握
懇親会
  • サイン色紙が全員に配られた。「有名人にいっぱいサインもらってください!」
  • 恥を捨てていろんなところに突撃してきましたw 皆様ありがとうございました。
  • Matzのところには大行列ができていた
  • 英語圏の方々とは話す勇気がなかった。いくじなし・・・
  • id:ReLaxの中の人と遭遇したのが最大のびっくり!*1

2日目

Ruby1.8のゆくえ (卜部さん)
  • 1.8.7 は 1.8.6 と compatible ではないと思われている
  • 1.8.7 => セキュリティやバグ修正優先
  • 1.8.8 => 下位互換の予定。1.8系の機能としてはこれで完成
  • 1.8.9 => 出ない。
  • 1.9移行へむけて、1.9の文法がParse Errorになるのをなんとかしたい
Ruby1.9.2 (Yuguiさん)
  • 1.9.1 => もうすぐPatch release
  • 1.9.2 => preview release 1 (時期を聞き逃した)
  • 1.9.2の変更は割と地味。地味なのはいいこと。better than 1.9.1
  • 地雷が結構埋まってるので注意
    • dl: i386べったりの実装。ffiを元に再実装したい。
    • ripper
    • tk
    • irb
  • ライブラリで内部関数は使わないで欲しい ex) ParseTree library
  • Ruby 1.8 => 1.9 の互換性は Perl 5 => 6 より高いw
  • 非互換なのは、CharsetやC API, M17Nなど
  • ライブラリもアプリも、移植を開始して欲しい
  • 不満はパッチに変えてMLへ!
Ramaze(Mr. Michael & Mr. Uehara)
  • Michaelは日本在住 => Ramazeは国産?!
  • Rubyらしい書き方でアプリケーションが作成できる
  • Rack以外の依存モジュールはなし => Rackとは密結合
    • # 多くのモジュールを必要とするRailsやMerbとは逆方向ですね
Keynote (松本さん)
  • lvar_propagateのキモさに度肝を抜かれた。だが、@wycatsが興味を持ったらしい。
    • その後、マジックコメントを入れた時だけ挙動が変わるようなパッチが入ったらしい。
  • 公平になりましょう。
LT
  • Rubyでギャルゲー@万葉のインパクトがすごすぎて、あとはなんも覚えてません。


調布花火大会に行くため、ここで離脱。
終了後のBeer bustでRejectKaigi2009#1が行われたらしい。
jugyoさんyoshukiさんが話したようだ。後で見る。

3日目

TermtterKaigi
  • 企画部屋で行われていたTermtterKaigiに参加。http://jugyo.org/blog/3517
  • termtter解説とか今後の課題とか。ちょっと課題を思いついたので、そのうちパッチ書く。
  • 個人的にCygwinで少し使えるようにしておきたいんだが、環境が自宅にないのが難点。誰かWindowsください。
Macruby
  • 安定性ならRubyCocoa, 使ってみたいならMacruby 0.4, Crazyな人はMacruby0.5
  • 1.9ベースで作られているが、Ruby再実装みたいなことになっている。人が足りない。
  • 内部ではObj-Cのオブジェクトを使っている。NSStringとか。
  • MultiCoreが使えるので、超はやい
  • AOT Compilerが使える
RubyMac OS X開発 & iPhone
Haml and Sass
  • Haml
    • %p&= @user.name で h メソッドと同じようにエスケープしてくれる
    • endを付けるとエラーになるが、メソッドチェイン時のみ書く事ができる
    • 読みやすいし書きやすい!
  • Sass
    • Nested Rules
    • 変数宣言や設定値の演算が可能
    • mixingが可能。引数渡しもできる。
  • 1年ほど実務で使っても特に問題は出ていない
  • ursm先生のHaml愛が伝わって来た。すごく嬉しそうだった。
Take the Red Pill(角谷さん)
  • Rubyの良さははっきりとは説明できない
  • Rubyのよさは楽しいと言い切ったところがすごい
  • 無名の質
  • Rubyの楽しさの先にあるものを共有する。月を差す指を見るのではなく、月を見る
  • より多くの人が満足するためには?
Keynote(高橋さん)
  • Ruby, 日本Rubyの会の振り返り
  • 引退宣言かと思ってあせったが、どうやら2人いたらしい。3人目かも。
番外編
  • yoshukiさんの経歴をかいま見る。熱い話でした。
  • tatsuosakuraiさんの奥さんは美人でできた人らしい。
  • takiuchiさんとメガネが一緒だった!メガネスキーとしてはうれしい!!

まとめ

スタッフ、関係者の皆様、親しく接してくださった皆様、ありがとうございました。
また、参加者の方々お疲れ様でした!
来年はLTかスタッフ辺りから関わってみたいです!

後で見る

http://www.ustream.tv/rubykaigi/videos
ニコ動かなんかがアップされたらまとめよう。どれ見るべきか忘れる。

*1:訂正:id間違えてました。ごめんなさい。