PHP 高速化に関するメモ書き
THK Analytics の記事「THK Analytics が高速・低メモリな理由」の PHP 部分に関する詳細メモ。
PHP プログラムの見直しによる高速化
array_push()
今回の記事の中で ダントツ で遅い。
あまりにもダントツなので、一番最初に書く。
array_push( $array, "hoge" );
と
$array[] = "hoge";
を比較した場合、
ループで 1000 件程度のデータを入れただけで、
秒差が出てしまうほど(体感でも分かってしまうレベルで)、array_push() の方が遅い。
array_search()、in_array()
array_search() もクソ って言葉を付けたくなるほど、マジ遅い。
こちらも、10000件ほどのデータを入れると8~9秒くらいクッソ遅くなる。
in_array() も、なかなか遅い。
例えば、
$array = array(
'apple',
'orange',
'melon',
'banana'
);
という配列があって、
if( in_array( 'orange', $array ) ) { ~ }
とかするなら、
$array = array(
'apple' => true,
'orange' => true,
'melon' => true,
'banana' => true
);
↑ こーいう連想配列にしちゃって、
if( isset( $array['orange'] ) ) { ~ }
もしくは、
if( array_key_exists( 'orange', $array ) ) { ~ }
とかにした方が、断然速い。
isset と array_key_exists() なら、あまり差がない。
isset は、変数が定義されてるかのチェックが入り、
array_key_exists() は、関数呼び出しのオーバーヘッドがある。
この分の差で、わずかに isset が速い。
※ isset は関数ではなく言語構造なので速いです。
配列に Null 値が入ってると返り値が違うので使い分けるのがいい。
結果が同じなら、個人的には isset 使う。
ていうか isset 最強 w
intval()、floatval()、strval()、boolval()
$hoge1 = intval( $aho );
$hoge2 = strval( $aho );
とするよりも、
$hoge1 = (int) $aho;
$hoge2 = (string) $aho;
のように、単純にキャストした方が速い。
なぜなら、intval()、floatval()、strval()、boolval() は関数を呼び出してるから。
比較演算子の ==、!=
返り値の型が明確に分かってるなら、
if( $hoge == $aho ) { ~ };
if( $hoge != $aho ) { ~ }
よりも
if( $hoge === $aho ) { ~ };
if( $hoge !== $aho ) { ~ }
の方が速い。
== や != は、緩やかな比較
=== や !== は、厳密な比較
PHP の内部的には、== は多数の比較を行い。
=== は厳密に極まった型だけを比較するだけで済む。
詳しくは、PHP: PHP 型の比較表 – Manual 参照。
返ってくる値が、
- 1 なのか true なのか
- 0 なのか false なのか
- null なのか "" なのか
分からん ∩(・ω・)∩ って時は仕方ないので緩やか比較使う。
Null かどうかの判断
Null かどうかだけ判断したい場合は、
最速 | if( $hoge !== null ) { ~ } | 厳密な比較 |
高速 | if( isset( $hoge ) ) { ~ } | 変数定義のチェックあり。ちょい厳密 |
速い | if( !empty( $hoge ) { ~ } | 変数定義のチェックあり。ちょい緩 |
遅い | if( !$hoge ) { ~ } | めちゃ緩い比較 |
最遅 | if( !is_null( $hoge ) ) { ~ } | 関数呼び出しのオーバーヘッドあり |
※ isset や empty は関数ではなく言語構造なので速いです。
上記は、あくまで Null のチェックであって、
"" や false や 未定義変数 などをチェックする場合は、
それぞれ返ってくる値も異なるので注意。
インクリメントとデクリメント
$i++ より ++$i
$i-- より --$i
の方が速い。
count() とか strlen() とか
配列の要素数を数えたりするために count()
文字列の長さを調べるために strlen()
を使ったりするのだが、
ぶっちゃけ、配列の要素数が 0 か否か、文字列の長さが 0 か否か
っていう判断をするだけなら、
if( count( $array ) === 0 ) { ~ }
if( !strlen( $string ) ) { ~ }
のような書き方するよりも、
empty で判断した方が速い。
なぜなら、empty は関数ではなく言語構造なので、関数呼び出しのオーバーヘッドがないから。
time()
$now = time();
より
$now = $_SERVER['REQUEST_TIME'];
の方が速い。なぜなら、関数呼び出しのオーバーヘッドがないから。
ダブルクォーテーションとシングルクォーテーション
正規表現などを使ってると分かるが、
シングルクォーテーション(') よりも ダブルクォーテーション(")
の方が特殊文字や変数の展開量が多いので遅くなる。
正規表現に限らず、どちらで書いても同じ結果なら
シングルクォーテーション(')の方が基本的には速い。
変数と文字列を連結する場合も、
$aho = 'あほ'
$hoge = "${aho}です"
よりも
$aho = 'あほ'
$hoge = $aho . 'です'
の方が速くなる。
文字列の連結
$hoge = $hoge . $aho
よりも
$hoge .= $aho
の方が速い。
また、上記「ダブルクォーテーションとシングルクォーテーション」でも書いた通り、
$hoge = "${hoge}${aho}"
は、最も遅い。
strpos() > strstr() >>>>> preg_match()
preg_match() は正規表現なので仕方ないとして、
単純に文字列中に特定の文字列が含まれているかどうかを調べるだけなら、
文句なしに strpos() が最速(大文字小文字の区別しないなら stripos() )。
ちなみに、文字列が含まれているかどうかを調べる場合、
if( strpos( $hoge, 'あほです' ) ){ ~ }
if( strpos( $hoge, 'あほです' ) !=0 ){ ~ }
if( strpos( $hoge, 'あほです' ) !==0 ){ ~ }
上記3パターンは正しい結果が返ってくるとは限らない。
if( strpos( $hoge, 'あほです' ) !==false ){ ~ }
と書かないとダメ。詳しくは、PHP: strpos – Manual 参照。
文字列から一文字だけ取得する場合
少し特殊な書き方ではあるが、
$hoge = substr( $aho, 5, 1 )
よりも
$hoge = $aho[5]
ってした方が速い。
正規表現
PHP に限らず、正規表現は重くて遅い。
どうしても、正規表現でなければダメな場面は多いが、
例えば、文字列全部が数字かどうか?
って言う場合、
if( preg_match( "/^[0-9]+$/", $hoge ) ) { ~ }
よりも、
if( ctype_digit( $hoge ) ) { ~ }
の方が当然、速い。
なので、このレベルの判別ならば、
- ctype_alnum()
- ctype_alpha()
- ctype_digit()
- is_int()
- is_numeric()
などで十分。
str_replace() >>>>> strtr() >>>>> preg_replace()
言うまでもない。
str_replace() で可能なら preg_replace() を使う理由はない。
ただ、分かっていても、正規表現を連続して使ってる場面で、
str_replace() で出来るにも関わらず、つい preg_replace() を使ってしまった。
なんてこともあり得るので注意。
strtr() が使える場面ならば、strtr() が最速。
最近、計測しなおしたら、同じ条件でも str_replace() の方が速い
print より echo
print は 1 を返す(常に1)が、echo は返り値がない。
返り値がない分、echo の方が速い。
そもそも、print が返す値が必ず 1 なので、
返り値を取得しても、全く嬉しくない。
変数に同じ値を入れる場合
$hoge1 = $hoge2 = 'aho'
と、1行で書くよりも
$hoge1 = 'aho'
$hoge2 = 'aho'
のように、素直に複数行で書いた方が速い。
そもそも、複数行で書いた方が、あとあと見やすい。
フルパスと相対パス
include やら require やら file やらに関しては、
フルパスで指定した方が速い。
include と require
require の方が速いが微差。
ただし、require は失敗すると、そこで処理が終了しちゃうので、
必ず成功させないとダメ。
include は失敗しても処理が続く利点はある・・・と言えばある。
file() と file_get_contents()
配列格納と文字列取得という挙動そのものが違うが、
同じ結果を求めるという前提であれば、
比較すること自体がおこがましいと言えるレベルで、
file_get_contents() の方が圧倒的に速い。
三項演算子
昔は遅かったが、今では、if・・・else・・・ とほぼ同等になってる。
(場合によっては、三項演算子の方が速くなることすらある)
だが、ぶっちゃけ if・・・else・・・ の方が平均的には、ちょい速
単純な処理ならば三項演算子で書けば1行で済むので、使うことも多い。
ループ内などでは使わない方が吉?かもしれない。
正直言えば、どっちでもいい w
if 文 と switch case
switch case で書ける場面ならば、switch case の方が速い。
しかも見やすい。
for 文よりも while 文
for() より while() の方が速い。
foreach() と while() は速度とメモリ消費量を見ながら選択。
配列操作は、foreach() が最も優秀で速いのだが、
数万件のデータ量だと while() の方がメモリ消費量が少なくなる。
ループ
当たり前と言えば当たり前なのだが、
for()、while()、foreach() など
ループの外で出来るものは先に処理しておく。
ディスカッション
コメント一覧
1点質問させていただきたいのですが、
なぜ言語構造は関数呼び出しと比較して速いのでしょうか?
どうぞよろしくお願いいたします。
PHP で速い順は、
言語構造 > ビルトイン関数 > 通常の関数
ですね。
言語構造とビルトイン関数は、基本的・汎用的な演算処理を行うために予め PHP のコアに組み込まれているから速いのです。
言語構造とビルトイン関数の違いは、
ビルトイン関数は PHP に予め組み込まれた関数であり、言語構造は PHP に予め組み込まれた「関数のような振る舞いをするキーワード」のようなものです。
PHP の言語構造には
echo, print, isset(), empty(), include, require 等
があります。
PHP のビルトイン関数には、
explode(), sort(), getdate() 等
がありますね。
ちなみに言語構造は「関数ではない」ため、
こうしても「未定義」と表示されます。