他の言語同様、PHPにも多くの高速化Tipsがあります。汎用で効果的なもの、守銭奴的で本質的に意義の乏しいものなど様々ですが、中には「全く無意味なこと」や「過去のバージョンの話」、「状況次第では逆効果になる」ようなものも散見されます。
先日は、にわかに高速化づいているGoogleも、をオープンさせました。その中には、も含まれています。ところが、これも「オカルト」に毒されたされ、公開から僅か1週間で何度も書き直されるような事態になっています。
ここでは巷間に溢れるPHP高速化Tipsの幾つか怪しいものを検証してみます。
※なお、検証に使ったPHPのバージョンは5.2.9です。
大きな配列はポインタで渡す?
PHPには厳密な意味でのポインタは無く、zvalのリファレンスということになりますが。
関数内で元の変数を参照するだけの場合でも、変数のコピーが行われないように参照渡しにする開発者を見かけます。しかし、少しPHPの内部構造を学べば、copy-on-writeの仕組みによってその必要が無いということを知っていると思います。
しかし、実際にはやはり値渡しとポインタ渡しは、値の変更が無かったとしても「同じ」ではありません。
関数内での変数の扱い方によって、速度が変わることを確認してみましょう。
foreach
まずは、foreachを使って大きな配列にアクセスしてみます。
function test1($a){ $c = 0; foreach($a as $v){ if($v=='xxxxx') ++$c; } } function test2(&$a){ $c = 0; foreach($a as $v){ if($v=='xxxxx') ++$c; } } $x = array_fill(0, 1000000, 'xxxxx'); test1($x); test2($x);
時間を測るあたりのロジックは割愛しますが、以下、結果です。
time: 0.627353906631 time: 0.374164819717
参照で渡した方が速くなっています。これは結局、test1でforeachが値のコピーを作る際に配列全体のコピーを行ってしまうためです。メモリ使用量を考えても参照渡しの方が優位ですが、これほどの巨大な配列でなければ気にするほどではないと思います。
for
次に、foreachではなくfor文を用いて、変数のコピーを行わずにアクセスするパターンを見てみましょう。
function test1($a){ $cnt = count($a); $c = 0; for($i=0; $i<$cnt; ++$i) if($a[$i]=='xxxxx') ++$c; } function test2(&$a){ $cnt = count($a); $c = 0; for($i=0; $i<$cnt; ++$i) if($a[$i]=='xxxxx') ++$c; } $x = array_fill(0, 1000000, 'xxxxx'); test1($x); test2($x);
処理としてはforeachのものと同じです。結果は、次のようになりました。
time: 0.508368015289 time: 0.736795186996
処理速度が逆転してしまっています。これは逆にメモリコピーが行われなくなれば、リファレンスやfunction-stackの追跡が挟まる分だけ少し遅くなってしまうということです。
for + count
更に、もう1つ確認してみましょう。
function test1($a){ $c = 0; for($i=0; $icountをループの外に出さず、毎回評価しています。これも高速化Tipsの基本ですが、ついつい手抜きをして、このようなコードを書いてしまう人も多いのでは。
この実行結果は、次の通りです。time: 0.00851798057556 time: 20.5319070816驚くべき結果になりました。
実はこのテスト、前のものに比べて配列のサイズを2桁下げています。test1の所要時間が短くなっているのはそのためで、決してこちらの書き方が高速というわけではありません。
それにしても、このtest2の眼を疑うような劣化ぶりは、どうしたことでしょうか。良かれと思ってわざわざ参照渡しにしたはずが、悲惨なまでにパフォーマンスを劣化させる結果になってしまいました。結論としては、関数内で値を直接変更しなければいけないような必要性が無ければ、PHPのcopy-on-writeに委ねたコーディングをするのが正解だと思います。
そして、countはちゃんとループの外に出しましょう
Related posts:
2009 年 7 月 4 日