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:これの呼び方がよくわからない