12のPHP最適化テクニック:phpspot開発日誌が参照にしている、12 PHP optimization tips - Alex Moskalyuk Blogからリンクされている、Ilia Alshanetsky (Ilia Alshanetsky - Talks)氏のPHP & Performance、PHPのチューニングの部分のみ読んだ、つもり。英語ダメなんで。
以下PHPのチューニングに関する部分のみのまとめ的なもの。私的なメモ。誤訳の可能性を考えると信頼はしないほうがよさげ。私以外の人は原文を読むべき。
Now let's tune PHP code (PHP & Performance P32-67)についてのメモ
オブジェクトについて
PHP5関連の話。
staticなメソッドは、常に宣言を行うべき
単純にそのほうが速くなる。
オブジェクト定数を活用する
- コンパイル時にオーバーヘッドがなくなる
- ハッシュが短くなると早く見つけることができる
- 名前空間を使って短いハッシュを使おう
- きれいなコードはデバッグしやすい
マジックメソッドは使わない
_get(), _set(), _autoload(), _call()は遅い。
require_once()は遅い
もしrequire_once()やinclude_once()を使わなければならないなら、せめてフルパスで指定すべき。
PHP5.2以降では同じファイルを二度読み込むことはなくなるだろう。
定数で代用できる関数は定数のほうが速い
php_version()→PHP_VERSIONphp_uname('s')→PHP_OSphp_sapi_name→PHP_SAPI
西部で一番速いWin32判別法
$isWindows = DIRECTORY_SEPARATOR == '\\';
- 関数を使わずに済む
- WinXP,WinNT,Windows,Windows98,NT5.0などの違いを考慮せずに済む
- どこでも使える
time()を何度も使うより
time()を何度も使うより$_SERVER['REQUEST_TIME']を使うほうが良い。関数を使わずに済む。
文字列置換・比較のチューニング
正規表現のチューニング
"後方参照しないパターン"を使う
以下の2つのコードについて……
$text = preg_replace('/=?\([^?]+)\?/', '=?iso-8859-1?', $origtext);
$text = preg_replace('"/(\n|\t|\r\n|\s)+/"', ' ', $origtext);
サブパターンの最初に?:をつけると後方参照しないパターンになる
メモリの節約になってお得
$text = preg_replace('/=?\(?:[^?]+)\?/', '=?iso-8859-1?', $origtext);
$text = preg_replace('"/(?:\n|\t|\r\n|\s)+/"', ' ', $origtext);
可能なら正規表現を使わない
if (preg_match("!^foo_!i", "FoO_")) { }
より
if (!strncasecmp("foo_", "FoO_", 4)) { }
のほうがかなり速い
if (preg_match("![a8f9]!", "sometext")) { }
より
if (strpbrk("sometext", "a8f9")) { }
のほうが速い
if (preg_match("!string!i", "text")) { }
より
if (stripos("text", "string") !== FALSE) { }
のほうが速い
$text = preg_replace("/\n/", "\\n", $text);
などはstr_replaceに置き換えるとより単純で高速。
$text = str_replace("\n", "\\n", $text);
しかし…
strtrを使う
上の例よりstrtrを使った場合のほうが速い。
とくに文字列を使ってstrtrを使う
$rep = array('-' => '*', '_' => '*');
if (sizeof($globArr) > 1) {
$glob = '-' . strtr($globArr[1], $rep);
} else {
$glob = strtr($globArr[0], $rep);
}
if (sizeof($globArr) > 1) {
$glob = '-' . strtr($globArr[1], '-_', '**');
} else {
$glob = strtr($globArr[0], '-_', '**');
}
↑ 下のほうが10倍速い。
そもそも置換自体をあまりしないように
置換はメモリを喰うので、strposあたりを使って置換を行うべきか否か事前にチェックするべき。
より良い文字列比較の方法
良い方法
if (!strncmp(PHP_OS, 'WIN', 3)) { }
if (!strncasecmp(PHP_OS, 'WIN', 3)) { }
悪い方法
if (substr(PHP_OS, 0, 3) == 'WIN') { }
if (strtolower(substr(PHP_OS, 0, 3)) == 'win') { }
最悪
if (preg_match('!^WIN!', PHP_OS)) { }
if (preg_match('!^WIN!i', PHP_OS)) { }
オフセットを反映させて文字列を比較するとき
PHP5ではsubstr()を使わずに、substr_compare()を使うことで文字列の開始位置以外での比較ができる。
if (substr($class, -15) != 'text') { }
if (substr_compare($class, 'text', -15)) { }
↑ 2つは同じ意味。
@演算子は最悪
@使うくらいなら、
$old = ini_set('error_reporting', 0);
action();
ini_set('error_reporting', $old);
を使うべき。
定数を間違って使わない
$foo = array("bar" => 0);
$foo[bar] = 1;
↑ ナンセンス。
何が悪いか
- 定数のくせに小文字 (この辺よく訳せず)
- 2回ハッシュの検索が実行される
- E_NOTICEレベルのエラーが発生する
- 一時的な文字列が生成されている
$foo['bar']と正しく記述することで7倍速くなる
"無害な"エラー・メッセージを修復
全てのエラーは結果として
- 複合的な文字列を作成する
- 標準出力に出力する
- ファイルやsyslogに出力するかも
- PHP5.2以前ではメモリリークを生むかも
forループの判定に関数を使わない
for ($i = 0; $i < sizeof($array); $i++) { }
for ($i = 0; $i < count($array); $i++) { }
for ($i = 0; $i < strlen($string); $i++) { }
↑のように書くと毎回関数が呼ばれるから遅くなる。
車輪の再発明はしない
使いたいものがすでに用意されている可能性は十分にある。
なるべくフルパスを使う
フルパスのほうが速い。
参照を使ったトリック
多次元配列は参照を使うとシンプルで高速になる。
// 以下の例はアクセスのたびに2つのハッシュを検索しているため遅い
for ($i = 0; $i < 5; $i++)
$a['b']['c'][$i] = $i;
//参照を使うとより高速になる
$ref =& $a['b']['c'];
for ($i = 0; $i < 5; $i++)
$ref[$i] = $i;
迷信
以下は迷信。
- コメントを除去すると速くなる
- "を使うと'を使うより速くなる
- 参照渡しを使うと速くなる
- オブジェクトを使うと速くなる
- 三項演算子を使うとif...elseを使うより速くなる