1年ぶりにブログを書こうとすると、書き方とかはてなの使い方とか、もろもろ分からなくて困った@satooshi_jpです。しかも今日はただの備忘録。

前フリ

PHP 5.4も出て、array呼び出しもできるようになったりして、PHPでは色んなメソッド(あるいは関数)の実行方法があります。で、一番速いのはどれで、古いバージョンでも動くのはどれだっけ?というのをまとめておくだけのメモです。

2012/04/28 __invoke()を追加

テスト環境

  • Windows 7 64bit
  • Core i5 M520 2.40GHz
  • Mem 8GB
  • PHP 5.4.0 (cli)

テストコード

<?php
class Command
{
public function execute($arg)
{
}
}
class Invoker
{
public function __invoke($arg)
{
}
}
// runner
function timer($closure)
{
$memStart = memory_get_usage();
$start = microtime(true);
$closure();
$diff = microtime(true) - $start;
$memDiff = memory_get_usage() - $memStart;
echo sprintf('time: %s sec mem: %s B' . PHP_EOL, $diff, $memDiff);
}
function performIteration($iteration)
{
timer(
function () use ($iteration)
{
for ($i = 0; $i < $iteration; $i++) {
}
}
);
}
function performCallUserFuncArray($iteration, $obj, $arg)
{
timer(
function () use ($iteration, $obj, $arg)
{
for ($i = 0; $i < $iteration; $i++) {
call_user_func_array($obj, array($arg));
}
}
);
}
function performCallUserFunc($iteration, $obj, $arg)
{
timer(
function () use ($iteration, $obj, $arg)
{
for ($i = 0; $i < $iteration; $i++) {
call_user_func($obj, $arg);
}
}
);
}
function performArrayInvocation($iteration, $obj, $arg)
{
if (PHP_VERSION < '5.4.0') {
return;
}
timer(
function () use ($iteration, $obj, $arg)
{
for ($i = 0; $i < $iteration; $i++) {
$obj($arg);
}
}
);
}
function performClosure($iteration, $callback)
{
timer(
function () use ($iteration, $callback)
{
for ($i = 0; $i < $iteration; $i++) {
$callback();
}
}
);
}
function performMethodName($iteration, $command, $arg, $methodName = 'execute')
{
timer(
function () use ($iteration, $command, $arg, $methodName)
{
for ($i = 0; $i < $iteration; $i++) {
$command->$methodName($arg);
}
}
);
}
function performInvoker($iteration, $invoker, $arg)
{
timer(
function () use ($iteration, $invoker, $arg)
{
for ($i = 0; $i < $iteration; $i++) {
$invoker($arg);
}
}
);
}
function performMethod($iteration, $command, $arg)
{
timer(
function () use ($iteration, $command, $arg)
{
for ($i = 0; $i < $iteration; $i++) {
$command->execute($arg);
}
}
);
}
// setup
$iteration = 100000;
$command = new Command();
$invoker = new Invoker();
$arg = '';
$closure = function () use ($command, $arg)
{
$command->execute($arg);
};
$obj = array($command, 'execute');
performIteration($iteration);
performCallUserFuncArray($iteration, $obj, $arg);
performCallUserFunc($iteration, $obj, $arg);
performClosure($iteration, $closure);
performArrayInvocation($iteration, $obj, $arg); // PHP >= 5.4.0
performMethodName($iteration, $command, $arg);
performInvoker($iteration, $invoker, $arg);
performMethod($iteration, $command, $arg);

結果

test case | elapsed time (sec) | memory usage (B) --------------------------|--------------------|------------------ performIteration | 0.0058639049530029 | 96 performCallUserFuncArray |0.13279795646667 | 112 performCallUserFunc | 0.11122488975525 | 112 performClosure | 0.076343059539795 | 112 performArrayInvocation | 0.074241876602173 | 96 performMethodName | 0.071202039718628 | 96 performInvoker | 0.039402961730957 | 96 performMethod | 0.037580013275146 | 112

まとめ

call_user_func()って遅いよねー。でも5.2みたいな古いPHPだとこれぐらいしか選択肢がないんだよねー。

もちろん、直接メソッド呼び出しするのが一番速い。でも、どうにかして関数オブジェクトみたいなものを渡さないといけない状況に遭遇して、どうやって関数を渡そうか迷った場合は、とりあえずclosureに入れて渡しておくのが一番無難な選択肢だ。

意外だったのが、メソッド名を変数にした場合、closureの実行とほとんどパフォーマンスが変わらなかったこと。実行時に色々探さないといけないから??

さらにもう一つ。__invoke()を忘れていたので、追加した。通常のメソッド呼び出しとほとんど変わらない結果になった。PHP 5.4だとそれほど差はなかったけど、PHP 5.3だと__invoke()が通常のメソッド呼び出しよりも速かった。なんでだろう??

第一案

  • 普通にメソッドを実行
  • __invoke()のオブジェクトを作っておく

第二案

  • メソッド名を指定
  • 配列呼び出し
  • closure

第三案

  • call_user_func()
  • call_user_func_array()