PHP - ちょっとベンチマーク

PHPってインタプリタだけど中間コード生成するよなあ〜その際にオプチマイズってしてくれるのかな〜

実行結果(レンタルサーバでは負荷をかけられないので自宅の Solaris x86で実行)

【ループ】
- count を毎回コールする
loop1 を 5000 回実行した際にかかった時間: 0.09086 ms
- ループの前にカウントしておく
loop2 を 5000 回実行した際にかかった時間: 0.04983 ms
- foreach を使う
loop3 を 5000 回実行した際にかかった時間: 0.03510 ms

【配列】
- array_push を使う
array1 を 1000 回実行した際にかかった時間: 0.14758 ms
- [] を使う
array2 を 1000 回実行した際にかかった時間: 0.10397 ms

【インクリメント】
- $i++ を使う
inc1 を 2000 回実行した際にかかった時間: 0.03648 ms
- ++$i を使う
inc2 を 2000 回実行した際にかかった時間: 0.03386 ms

最適化を考慮したコーディング

PHPってよくいえば律儀にインタプリトするのね・・・ちょっと高速化を考慮したコーディングしないと駄目ですね。

【PHP特有の最適化】
  • print より echo を利用したほうが速い
  • echo の文字列表示はドットによる連結('文'.'字')よりカンマ区切り('文','字')の方が速い
  • static にできるメソッドは static として宣言した方が速い
  • 大きな配列変数は、必要がなくなったら unset()してメモリを解放する
  • ループの最大値は、ループの前にセットしておく
  • ループ処理には foreach を利用する
  • for($i=0; $i <= count($array); $i++)というように for文に count を記述すると毎回 count が動くので、forの前にカウントして変数に入れておく
  • マジックメソッド(__get, __set, __autoload,...)は利用しない
  • 正規表現の利用は極力避ける(strncasecmp, strpbrk, striposといった文字列操作系の関数を利用する)
  • htmlspecialchars_decodeは大量のデータの変換は遅い
    PHP4で行っていたころのように function htmlspecialchars_decode4($string, $quote_style=ENT_COMPAT) {
        return strtr($string, array_flip(get_html_translation_table(HTML_SPECIALCHARS, $quote_style)));
    }
    とstrtrとarray_flipを利用した方が速い
  • str_ < preg_ < ereg_ < mb_ereg とかかる時間の大きさの図式になる(リプレースを考えると strtr < str_replace < preg_replaceとなる、そもそも ereg_系は利用しない)
  • @によるエラー制御は利用しない
  • インクリメント: ローカル変数 < グローバル変数 < オブジェクト変数($this->prop++)とかかる時間の大きさの図式
  • インクリメント: $i++ より ++$i の方が速い
  • if (strlen($foo) < 5){} なら if (!isset($foo{5})) {} というように isset を利用したほうが速い
  • 処理に時間のかかる複雑な関数は拡張モジュール化にする(PECL)
  • もともと用意されている関数を利用する(用意されている関数は C で実装されているため)
  • 一時的なファイルを作るときには tmpfile や tempnam を利用する
  • パスなどの設定パラメータは配列を serialize して キャッシュしておく
  • define()を大量に記述した設定ファイルをロードするより parse_ini_file() を利用する
  • アクセスの多いページには PHP の 出力バッファリング を利用する
【ちょっと迷う最適化】
  • require_onceの利用は処理を遅くするが、コードの保守性や安全のためには利用すべき?
  • include, require で指定するファイルはフルパスで記述するのがよいが、コードの保守性のためには?
  • ダブルクォート より シングルクォート の方が少し速い
  • すべてを OOPする必要はない(メモリを多く消費してしまうかも)
【環境の最適化など】
  • キャッシュの利用(memcachedや APC、XCache、eAccelerator)
  • 通信量を減らすために Apache の mod_gzip/mod_deflate を利用する
  • Apache の allowoverride を "none" にする
  • xdebugなどを利用してコードのプロファイリングを行う

ソースコード

<?php
require_once("Benchmark/Iterate.php");

define(MAX_RUN5000);

$data = array();
for (
$i=0$i<100$i++) {
    
$data[$i] = $i;
}

header("Content-Type: text/html;charset=euc-jp");

echo 
"<html>\n";
echo 
"<head>\n";
echo 
"<title>Benchmark</title>\n";
echo 
"</head>\n";
echo 
"<body>\n";

echo 
'【ループ】<br>';
echo 
'- count を毎回コールする<br>';
doBenchmark('loop1'$data5000);
echo 
'- ループの前にカウントしておく<br>';
doBenchmark('loop2'$data5000);
echo 
'- foreach を使う<br>';
doBenchmark('loop3'$data5000);
echo 
'<br>';

echo 
'【配列】<br>';
echo 
'- array_push を使う<br>';
doBenchmark('array1'$data1000);
echo 
'- [] を使う<br>';
doBenchmark('array2'$data1000);
echo 
'<br>';

echo 
'【インクリメント】<br>';
echo 
'- $i++ を使う<br>';
doBenchmark('inc1'$data2000);
echo 
'- ++$i を使う<br>';
doBenchmark('inc2'$data2000);
echo 
"</body>\n";
echo 
"</html>\n";

exit;

/**
 * ベンチマーク関数
 *
 * @param  function  $sFuncName     関数名
 * @param  array     $aryData       関数に渡す配列
 * @param  integer   $iCount        実行回数
 */
function doBenchmark($sFuncName=null$aryData=null$iCount=MAX_RUN)
{
    
reset($aryData);
    
$benchmark = new Benchmark_Iterate;
    
$benchmark->run($iCount$sFuncName$aryData);
    
$result $benchmark->get();
    
printf("%s を %d 回実行した際にかかった時間: %.5f ms<br>",
        
$sFuncName,
        
$result['iterations'],
        
$result['mean'] * 1000);
}

// count を毎回コールする
function loop1($aryData null)
{
    for (
$i =0$i count($aryData); $i++) {
        
$sum += $aryData[$i];
    }
}

// ループの前にカウントしておく
function loop2($aryData null)
{
    
$max count($aryData);
    for (
$i =0$i $max $i++) {
        
$sum += $aryData[$i];
    }
}

// foreach を使う
function loop3($aryData null)
{
    foreach (
$aryData as $data) {
        
$sum += $data;
    }
}

// array_push を使う
function array1($aryData null)
{
    
$aryBuff = array();
    foreach (
$aryData as $data) {
        
array_push($aryBuff'<!-- '.$data.' -->');
    }
}

// [] を使う
function array2($aryData null)
{
    
$aryBuff = array();
    foreach (
$aryData as $data) {
        
$aryBuff[] = '<!-- '.$data.' -->';
    }
}

// $i++ を使う
function inc1($aryData null)
{
    
$aryBuff = array();
    foreach (
$aryData as $data) {
        
$i++;
    }
}

// ++$i を使う
function inc2($aryData null)
{
    
$aryBuff = array();
    foreach (
$aryData as $data) {
        ++
$i;
    }
}
?>