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