PHPコード最適化Tipsのウソと本当(解説)

PHP コード最適化 Best Practices 63+ - カタコト日記

前回は、元記事に一定の敬意を表して、項目とかはあえてそのままにしてたんですが、

自分としても気になる部分が多々あったので、少しだけ調べ直して優先度&解説つけました。

独断と偏見ですが。ヽ(´ー`)ノ 検証はしてません。ごめんなさいごめんなさい。


優先度A、B、C、不明、非推奨に分けてみました。どうぞつっこんでください。

長いよ、今回は。

優先度A. 頻度も高いし使えそう - 6つ

01. static にできるメソッドは static として宣言しよう。(4倍速い)

正しくは、static なメソッドには、OOP のルールに従ってちゃんと static 宣言をつけよう!
ってとこでしょうか。本来そうでないものを無理に static にしちゃえって話ではないはず。*1

× <?php public function staticMethod() {}   // 静的に動作するメソッド(インスタンス経由で呼び出される)
○ <?php public static function staticMethod() {} // 宣言された静的メソッド(直接、呼び出される)


似た原理のTips: クラス内で定数代わりに使ってる変数があるなら、const にしよう。
(コンパイル時に1度だけパースされ、実行時のオーバーヘッドがなくなります)


04. ループの最大値は、ループ「内」ではなく「前」にセットしておこう。
19. for 文の条件式には count($array) のような関数をいれない。(変数に格納)

ループのたびに計算することになって無駄だから。紹介されてた例:

× <?php for ($j = 0; $j < count($arr); $j++) {}
â—‹ <?php for ($j = 0, $max = count($arr); $j < $max; $j++) {}
10. 可能であれば、正規表現より strncasecmp、strpbrk、stripos を使おう。
11. strtr(str_replace の4倍速い) > str_replace > preg_replace の順に速い。
50. 可能なら正規表現より、ctype_alnum、ctype_alpha、ctype_digit を使おう。

正規表現は便利だけど負荷がかかるってことですね。

紹介されてた例:

× <?php if (preg_match("!^foo_!i", "FoO_")) {}
â—‹ <?php if (!strncasecmp("foo_", "FoO_", 4)) {}


へぇーと思った例:(注:stripos は str_replace よりかなり高速)

<?php
$src_str = file_get_contents("BIG_FILE"); 
$src = array('abc', 123, 'text'); 
$dst = array('cba', 321, 'txet'); 

// BIG_FILE 中に置換対象となる文字たち $src が入ってないかを strpos() で探す
foreach ($src as $k => $v) {
        // 入ってない場合(false)は、処理しない(unset() して置換対象からはずす)
        if (strpos($src_str, $src) === false) {
                unset($src[$k], $dst[$k]);                 
        }
}
if ($src) { $new_str = str_replace($src, $dst, $src_str); }

二度手間なのにこっちの方が速いらしい。(1回の置換操作に時間がかかるような場合でしょうけど)
ファイル全体に対してループでぐるぐる置換しまくってるような箇所があれば応用できそう。


39. もとから用意されてる 関数たち を活用しよう。
53. 一時的なファイルを作るときには tmpfile や tempnam を使おう。

関数だけじゃなくって、マニュアル もよく読もう。(ここに書かれてある Tips って、
実はマニュアルに書かれてることばっかだったり)あと、自分で何か作り始める前に
PEAR とか PECL とかに似たのがないかもチェック。例として挙ってた便利な関数たち:

  • file_get_contents …ファイルの内容を全て文字列に読み込む
  • file_put_contents …文字列をファイルに書き込む
  • microtime …現在の Unix タイムスタンプをマイクロ秒まで返す
  • gettimeofday …現在の時間を得る
  • convert_uuencode …文字列を uuencode する
  • convert_uudecode …uuencode された文字列をデコードする
  • http_build_query …URL エンコードされたクエリ文字列を生成する
  • substr_compare … 指定した位置から指定した長さの 2 つの文字列について、バイナリ対応で比較する
  • array_walk_recursive …配列の全ての要素に、ユーザー関数を再帰的に適用する

ファイルの内容を読み込む例:

× <?php // 昔はこうやって書いてた
$data = '';   
$fp = fopen("some_file", "r"); 
while ($fp && !feof($fp)) { 
        $data .= fread($fp, 1024); 
}
fclose($fp); 
○ <?php $data = file_get_contents("some_file"); // 今はこの関数ひとつで(しかも速い)
18. エラーメッセージはハイコスト。
55. デバッグするときは error_reporting (E_ALL); にしておこう。
17. $row['id'] は $row[id] より7倍速い。
23. 未定義のローカル変数のインクリメントは定義されたものより9〜10倍遅い。
24. 未定義のグローバル変数についても同様。

エラーレベルが E_ALL か E_STRICT でもエラーが出ないようにしておけばこれらすべて回避可能。


ちなみに 17番。$row[id] ではまず、id が定数と解釈されて、それが登録されてるかが走査されます。
でも見つかんないので、やれやれと E_NOTICE を吐きつつ文字列に解釈し直してくれるようです。
下位互換性のための挙動。動作はするけど、無駄だらけってことですね。*2


31. キャッシュを利用しよう。通常、コンパイル回数 x 25〜100% 分速くなる。
32. これもキャッシュについて。memcached とか使おうよ。
58. パスなどの設定パラメータは配列に serialize して入れ、キャッシュしておこう。

キャッシュの効果自体は説明不要。でもどうせなら、どこをどれくらいの頻度でどこに
キャッシュするかみたいな情報の方が有益?


優先度B. 機会があれば / 迷ったら使うかな的 - 5つ

03. echo '文','字'; (カンマ区切り)の方が、'文'.'字' (ドット連結)より速い。

あまりそそられないんですけど、使う機会があれば。

08. include や require でファイルはフルパスで指定しよう。
51. ファイル指定は basename / file_exists / open_basedir よりフルパス指定が速い。

最近は、フレームワークがオートロードしてくれたりするおかげで
パスを書くことがあまりなくなりました。Zend_Loader 最高!

09. スクリプト開始時間は time() でなく $_SERVER['REQUEST_TIME'] で取得。

類似のものとして、

などなど。$_SERVER とか、定義済み定数 があるなら関数を呼ばずにそちらを使おう。

20. メソッド内ではローカル変数をインクリメントするのが一番速い。
21. グローバル変数のインクリメントはローカル変数より2倍遅い。
22. オブジェクト変数(例:$this->v++)のインクリメントはローカルより3倍遅い。

「がっつり」インクリメントしたいときはローカル変数に入れてから。

28. ダブルクォート より シングルクォート の方が若干速い。

速度のためというよりは、クォートの使い分けは普段からきちんとした方がいいかなと。
意味も違うし可読性も変わってくる。ここ とか参考に書き方を統一してはいかが。


優先度C. 実はほとんど効果がないけどね - 1つ

02. echo の方が print より速い。

print は返り値をセットするかららしいんだけど、この差はほとんど無視できるみたい。*3
逆に、ob_start を使ってるときは print の方が速いって話もあるみたいですね。*4


蛇足: print よりも "速い" echo ですけど、データの出力自体がそもそもすごく遅い処理です。

echo を連発するような機会が仮にあるなら、ob_start とかでバッファリングしてまとめて

出力するのがいいみたいですね。フレームワークとか使ってるなら自動でそうなってると思う。


優先度不明. 効果がいまいちわかんないです。誰か説明 / 検証してください (ノД`)シクシク - 3つ

05. 大きい配列のような変数は unset() してメモリを解放しよう。

正しそうっていうのはわかる。でも「大きい」ってどれくらい…?DB クエリの結果とか?
Web アプリだとメモリの解放とかあんまり意識しないんですけど、ダメな癖なんでしょうね。

07. require_once はハイコストなのです。
52. require_once より require を使おう。(opcode キャッシュがらみの理由で)

元ネタには require より3〜4倍遅いとありますが、別のベンチマーク では
真逆の結果が出てます。速度うんぬんよりやっぱり用途によって使い分けるべきでは?

16. 処理が終わったらデータベースの接続は切っておこう。

やらないと結構、違いが出るんでしょうか?
また接続したくなっちゃったら再接続コストの方が大きくなったりして。


非推奨? 速度の代償が大きすぎるんじゃ… - 3つ

06. マジックメソッド(例: __get, __set, __autoload)は使用を避けよう。

速度を求めて機能とか効率を犠牲にしてる感じが…。根拠を探したけど見つからず詳細不明。
別のベンチマーク(__autoload vs. require_once)では、__autoload が速かったとかってのもあったし。
そうでなくても、これで便利になって生産性が上がるなら気にせず使っちゃえばいいと思う。

33. if (strlen($foo) < 5) を調べたいなら if (!isset($foo{5})) と書くと速い。

へぇーと一瞬思ったけど、1年後くらいに見たらたぶん意味不明なんだろうなぁ。
ソースから恒常的にロジックが読み取れるって経験的にすごく大事なことだと思います。
追記:でもって、マルチバイトだと期待どおり動作しないらしいです。

34. $i++ より ++$i の方が速い。(そういう opcode optimizer が走ってる場合)

「そっかぁ ヽ(*´∀`)ノ」っていきなり $i++ を ++$i に置換したりしちゃダメですよ。
文脈によってはまったく 別物 なので。これも意味の汲み取りを妨げる微妙な Tips かなぁ。
++$i がそこで実装すべきロジックを反映したものか、速度を求めたおまじないなのかが曖昧になるし。



あとは独断で省略。(サーバ、DB、設計とか)それぞれかなり大きなトピックだと思うんだけど、
元記事にリストアップされてたのはかなり粗い寄せ集め的な印象を受けたので。
サーバのチューニングは特効薬になりうるし、DB まわり(設計とか SQL)は実際に
ボトルネックになってることも多いと思うから、まとめがあると役に立つと思うんだけど。
誰かやってくれないかなぁ、あっち系に強い方々。人まかせであれですが。( ´ー`)y-~~


あと、前回のエントリーにトラックバックをいただいた
http://d.hatena.ne.jp/hanageman/20080523
http://d.hatena.ne.jp/gegegen/20080524/1211622711
上に挙げた細かいテクニックよりも、一つ上のレイヤーの話なども。おもしろかったです。


今回、参考にした記事(前回と重複)

PHP & Performance By: Ilia Alshanetsky (PDF)
PHP & Performance By: Ilia Alshanetsky (PDF、タイトル同じですが別物)
Optimizing PHP Through Habits - Joakim Nygård
http://phplens.com/lens/php-book/optimizing-debugging-php.php


ちなみに、大元のネタとしていろんなサイトで引用されまくりの Ilia Alshanetsky さんは

PHP 開発チームの中の人みたいです。おぼえとこっと。

*1:インスタンスメソッドと静的メソッドのお話。5/27 例の部分を訂正 コメント欄もぜひ見てくださいね

*2:PHP: 配列 - Manual

*3:http://www.faqts.com/knowledge_base/view.phtml/aid/1/fid/40

*4:http://phplens.com/lens/php-book/optimizing-debugging-php.php