最近更新: 2007-01-09

PHP5 的動態函數/行為調用效率測試

Tags: variable_function magic_method reflection

我先前為了測試 PHP5 的 reflection 能力,找到《Benchmarking dynamic function/method calls》為參考文章,寫了一段效率測試碼。剛好今天看到 HACGIS 也做了《各種呼叫方式的比較》。因為 HACGIS 沒測到 reflection 的部份,所以把我的效率測試碼也放上來供各位參考。

<?php
class Benchmark {
    public $counter;
    public function __construct() {
        $this->counter = 0;
    }
    public function increase($c, $b, $a) {
       //echo 'counter a = ', $a, "\n";
       //echo 'counter b = ', $b, "\n";
       //echo 'counter c = ', $c, "\n";
       $this->counter += $c;
       return;
    }
}

$times = 1000000;
$benchmark = new Benchmark();
$unorderedParameters = array(
    'b' => 1,
    'a' => 100,
    'c' => 13
);

// Get an instance of the ReflectionMethod class by an ojbect
$funcName = 'increase';
$func = new ReflectionMethod($benchmark, $funcName);
if ($parameters = $func->getParameters()) {
    $i = 0;
    foreach ($parameters as $parameter) {
        $k = $parameter->getName();
        $orderedParameters[$i++] = $unorderedParameters[$k];
    }
}

printf("---> Invokation arguments: \n");
var_dump($unorderedParameters);

echo "Straight method call: \n";
$benchmark->counter = 0;
$start = microtime(true);
for ($i = 0; $i < $times; $i++) {
    $benchmark->increase($unorderedParameters['c'], $unorderedParameters['b'], $unorderedParameters['a']);
}
$end = microtime(true);
$elapsed = $end - $start;
echo 'result is ', $benchmark->counter, ', elapsed : ', $elapsed, ' secs', "\n";


echo "Variable method call: \n";
$benchmark->counter = 0;
$start = microtime(true);
for ($i = 0; $i < $times; $i++) {
    $benchmark->$funcName($unorderedParameters['c'], $unorderedParameters['b'], $unorderedParameters['a']);
}
$end = microtime(true);
$elapsed = $end - $start;
echo 'result is ', $benchmark->counter, ', elapsed : ', $elapsed, ' secs', "\n";


echo "invoke method call: \n";
$benchmark->counter = 0;
$start = microtime(true);
for ($i = 0; $i < $times; $i++) {
    $func->invoke($benchmark, $unorderedParameters['c'], $unorderedParameters['b'], $unorderedParameters['a']);
}
$end = microtime(true);
$elapsed = $end - $start;
echo 'result is ', $benchmark->counter, ', elapsed : ', $elapsed, ' secs', "\n";


echo "invokeArgs method call: \n";
$benchmark->counter = 0;
$start = microtime(true);
for ($i = 0; $i < $times; $i++) {
    $func->invokeArgs($benchmark, $orderedParameters);
}
$end = microtime(true);
$elapsed = $end - $start;
echo 'result is ', $benchmark->counter, ', elapsed : ', $elapsed, ' secs', "\n";


echo "call_user_func_array method call: \n";
$benchmark->counter = 0;
$start = microtime(true);
$m = array(&$benchmark, $funcName);
for ($i = 0; $i < $times; $i++) {
    call_user_func_array($m, $orderedParameters);
}
$end = microtime(true);
$elapsed = $end - $start;
echo 'result is ', $benchmark->counter, ', elapsed : ', $elapsed, ' secs', "\n";

?>

結論... reflection 這個在 Java/C# 中的高等技巧,在 PHP 這種動態語言中就顯得有些多餘了。當行為名稱及參數個數及順序已知時,傳統調用方法的效率無疑是最好的。當參數個數及順序未知時,使用 call_user_func_array() 也比 Reflection::invokeArgs() 來得好些。

PHP 函數參考
相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/2633503.html

樂多舊回應
HACGIS@gmail.com(tokimeki) (#comment-3838612)
Fri, 12 Jan 2007 07:04:42 +0800
說實話,反射相關的功能我一次也沒用過~

最近我在參考PEAR::Benchmark來寫一套量測效能用類別,目前只剩輸出報表以及註解文件的部份要完成。

我是把PEAR::Benchmark的三項功能全部寫在同一個類別中,目前的程式碼行數大約300行,除了某個指標(Benchmark_Profiler的$_subSectionsTime)我看不懂為何要計算之外,其他的都實做出來了。

過兩天我會在我的網誌上詳細解說。
未留名 (#comment-3838830)
Fri, 12 Jan 2007 09:32:03 +0800
Reflection 功能 ,或者簡單地說是「讓 object 看到自己的外貌」的功能。我當初是為了建立一個 PHP framework 而使用。

我在實踐一個 REST 頁面時,傾向不限定參數順序,如:
class Class1 {
function method1($a, $b) {
}
}

則 REST 調用:
http://example.com/class1/method1/paramA/1/paramB/abc

http://example.com/class1/method1/paramB/abc/paramA/1

都要對應到:
$this->method1('1', 'abc')

要達到這個要求,就必須知道參數宣告的順序,才能將使用者傳遞過來的內容按正確順序傳遞。以 PHP 目前的內建函數來看,只有透過 Reflection 才行。也就是上述測試碼第25-33行的工作。至於其他的 Reflection 功能,就比不上傳統函數了。

要探知有無某 class: class_exists()
要探知 class 有無某 method: method_exists(), is_callable()
要調用某 method: $obj->$method(), call_user_func(), call_user_func_array()

這些傳統函數都比 Reflection class 快。惟有探知參數順序這個需求沒有對應的傳統函數,不得不用 Reflection 。
HACGIS@gmail.com(tokimeki) (#comment-3839884)
Fri, 12 Jan 2007 17:00:47 +0800
原來你那段程式是在作這個用途阿~
不過每個人的習慣不同,我個人比較傾向於先安排好,然後在函數內作過濾參數動作,所以我也不會去想到有這個作法。
感謝你的分享,長了見識了~
Kuon@chroot.org(Kuon) (#comment-9555837)
Tue, 24 Apr 2007 02:49:47 +0800
不論是 Refelection 或是 Function Handling Functions, 其實都已經實作在某些 Framework 中, 像這種類的函數可怕在於其具有eval()的語意, 須特別注意安全性的議題.
未留名 (#comment-9716747)
Wed, 25 Apr 2007 11:42:03 +0800
關於 Kuon 的意見,我回應於《Reflection 於設計 Framework 時之安全性作用》。