PHP

PHPを少しでも速く動かしたい

More than 1 year has passed since last update.

色々調べると先人方が調べてくれています。
とはいえ、情報が散らばってるので個人的にまとめたかったのと
現状どのくらい効果あるのか知りたいので遊んでみる。
どのくらい効果あるのか検証は100回〜1000回計測での平均
PHPは5.6.30
VMで動かしてるからちょっと遅い^^;;;

インクリメントとデクリメント

\$i++とか\$i--とかを単体で下のように書くことはよくありますが

PHP
for($i = 0; $i < 100; $i++){ ... }

これ遅いらしい…
++とか--は後ろに書くより前に書けと。

PHP
//パターン1
for($i = 0; $i < 100000; $i++){}

//パターン2
for($i = 0; $i < 100000; ++$i){}

平均実行時間(s)
パターン1 0.20112862586975
パターン2 0.16215991973877

約1.24倍の高速化
なかなか手の癖で++を先に書くのって慣れないけど^^;;;


型変換は関数よりキャスト

PHP
//パターン1
$val = '1';
for($i = 0; $i < 100000; ++$i){
  $tmp = intval($val);
}

//パターン2
$val = '1';
for($i = 0; $i < 100000; ++$i){
  $tmp = (int)$val;
}

平均実行時間(s)
パターン1 0.10631687641144
パターン2 0.05006349086762

約2.1倍の高速化

関数とキャストを比べたら当然か^^;;;
intval()は入力値の基数を指定できるので、そういう必要があれば使いましょう。


数字(not数値)を計算で使うなら事前にキャストする

PHP
//パターン1
$val = '1';
$tmp = 0;
for($i = 0; $i < 100000; ++$i){
  $tmp += $val;
}

//パターン2
$val = '1';
$val = (int)$val;
$tmp = 0;
for($i = 0; $i < 100000; ++$i){
  $tmp += $val;
}
平均実行時間(s)
パターン1 0.46492505073547
パターン2 0.27117309570312

約1.7倍の高速化
パターン1は毎回、数字(string)から数値(int)へキャストされるので遅い。
さすがに、このサンプルのような書き方を実際にはしないと思うが、
設定ファイルやどっかからもってきた値で数値になってるとは限らない場合は、
キャストしておくほうが確実に速くなりますね。


定数よりグローバル変数を使う ではなさそうだ…

PHP
//パターン1
define( 'MIZUKI', 7 );
$tmp = '';
for($i = 0; $i < 100000; ++$i){
  $tmp = MIZUKI;
}

//パターン2
$GLOBALS[ 'MIZUKI' ] = 7;
$tmp = '';
for($i = 0; $i < 100000; ++$i){
  $tmp = $GLOBALS[ 'MIZUKI' ];
}

平均実行時間(s)
パターン1 0.027095818519592
パターン2 0.048010039329529

定数の方が速かった
定数として使うなら定数として定義すべきだと思うので、グローバル変数が速くても推奨できないなぁと思っていたが(´∀`)ホッ-3


ループはforよりwhile ではなさそうだ…

無限ループの危険があるwhileですが、forより速いという文献がちらほら。

PHP
//パターン1
for( $i = 0; $i < 100000; ++$i){}

//パターン2
$i = 0;
while($i < 100000){
  ++$i;
}
平均実行時間(s)
パターン1 0.17190592288971
パターン2 0.14495255947113

約1.19倍の高速化
見通しの良いコードが書けていれば不安はないけど、
インクリメント忘れてた怖いなと^^;;;


ループの継続判定条件に関数等を書かない

これは、効果検証するまでもなくループ毎の判定で関数が動くんだから遅いのは明白…

PHP
for($i = 0; $i < count( $array ); ++$i) { ... }

なんて書いてたらぶっ飛ばす^^;;;


ループ外に出せる処理は外でやる

PHP
//パターン1
for($i = 0; $i < 100000; ++$i){
  $key = array_search(1, array(1,2,3,4,5,6,7) );
}

//パターン2
$array = array(1,2,3,4,5,6,7);
for($i = 0; $i < 100000; ++$i){
  $key = array_search(1, $array );
}
平均実行時間(s)
パターン1 0.7890996217728
パターン2 0.3615311384201

約2倍の高速化
パターン1は毎回配列を生成してるので、普通に考えれば自明の理ですね。


比較演算子「==」や「!=」は「===」や「!==」で判定する

PHP
//パターン1
$val = 1;
for($i = 0; $i < 100000; ++$i){
  if($val == 1){ }
}


//パターン2
$val = 1;
for($i = 0; $i < 100000; ++$i){
  if($val === 1){ }
}
平均実行時間(s)
パターン1 0.26900029182434
パターン2 0.29546132087708

あれ?「===」の方が遅くなってる…
「==」の方が型変換が行われるから遅いんだよ(`・ー・´)ドヤ!
って書こうと思ったのに謎だ。


Bool値を条件判定に使うときは、そのまま使う

PHP
//パターン1
$bool = true;
for($i = 0; $i < 100000; ++$i){
  if($bool === true){ }
}

//パターン2
$bool = true;
for($i = 0; $i < 100000; ++$i){
  if($bool){ }
}

平均実行時間(s)
パターン1 0.29934306144714
パターン2 0.24208245277405

約1.2倍の高速化
大きな速度差は出なかったものの、
パターン1だと$bool === trueという$boolがtrueか否かを判定し
その結果をif文の判定へと投げているので、
パターン2のいきなりif文にtrueですけど判定してとした方が速い
という流れの違いは認識しておいた方がよいでしょう。


三項演算子よりif文

PHP
//パターン1
$hoge = null;
for($i = 0; $i < 100000; ++$i){
  $hoge = true ? 1 : 0;
}

//パターン2
$hoge = null;
for($i = 0; $i < 100000; ++$i){
  if( true ){
    $hoge = 1;
  }
  else{
    $hoge = 0;
  }
}

平均実行時間(s)
パターン1 0.0313186645508
パターン2 0.0309974193573

ほぼ同じ
ではあるのですが、実は何回も検証してみました。
結果、ほぼ同じ事はあってもif文の方が安定して速い。

昔、三項演算子の方が速い的な何かを見た記憶があり、
たしかに文より演算子の方が速そうだしと納得していたのでビックリ。

三項演算子の方が複数行にならないのでシンプルに見えるので…
色々思うところはあるし、ケースバイケースだとも思うのですが
若干宗教戦争がありそうなので沈黙^^;;;


if文は頻度の高い分岐側をelseにする

PHP
//パターン1
for($i = 0; $i < 100000; ++$i){
  if( true ){

  }
  else{

  }
}

//パターン2
for($i = 0; $i < 100000; ++$i){
  if( false ){

  }
  else{

  }
}


平均実行時間(s)
パターン1 0.023554921150208
パターン2 0.021920537948608

若干速い
が!無理にやると分かりにくいコードになるし、
そこまでしても、劇的に速くなるわけではないので。


switch文は頻度の高い条件を先に書く

PHP
//パターン1
for($i = 0; $i < 100000; ++$i){
  $val = 7;
  switch( $val ){
    case 1: break;
    case 2: break;
    case 3: break;
    case 4: break;
    case 5: break;
    case 6: break;
    case 7: break;
    default:
  }
}

//パターン2
for($i = 0; $i < 100000; ++$i){
  $val = 1;
  switch( $val ){
    case 1: break;
    case 2: break;
    case 3: break;
    case 4: break;
    case 5: break;
    case 6: break;
    case 7: break;
    default:
  }
}

平均実行時間(s)
パターン1 0.117320728302
パターン2 0.042806816101

約2.7倍の高速化
この検証はあくまで速度差がどの程度でるかという点なので
実際はそれなりに各caseに分岐するだろうから
速度という面では銀の弾丸でないです。
ただ、可読性的に悪くならないのであれば、頻度の高いcaseを
上の方に書いた方がいいよねと心にしまっておいても損はないでしょう。


文字列は「.」で連結

PHP
//パターン1
$str = "水樹奈々";
for($i = 0; $i < 100000; ++$i){
  $tmp = "スマイルギャング $str";
}

//パターン2
$str = "水樹奈々";
for($i = 0; $i < 100000; ++$i){
  $tmp = "スマイルギャング " . $str;
}

平均実行時間(s)
パターン1 0.64041557312012
パターン2 0.50412213802338

約1.27倍の高速化
””の中に変数展開するより連結した方が速そうなのは雰囲気的にもわかる。


文字列が入っている変数への連結は「.=」

PHP
//パターン1
for($i = 0; $i < 100000; ++$i){
  $str = "水樹奈々 ";
  $str = $str . "スマイルギャング";
}

//パターン2
for($i = 0; $i < 100000; ++$i){
  $str = "水樹奈々 ";
  $str .= "スマイルギャング";
}

平均実行時間(s)
パターン1 0.057024383544922
パターン2 0.053062963485718

気持ち速い
PHPの内部を追っていないので詳細は不明ですが、
連携して、その結果を代入という手順より速い中間コードになっているでしょう。

それほど差は無いので、どちらでもいいかなぁ。


文字列からの1文字抽出は配列として抽出する

PHP
//パターン1
$str = "mizukinana";
for($i = 0; $i < 100000; ++$i){
  $val = substr( $str, 7, 1 );
}

//パターン2
$str = "mizukinana";
for($i = 0; $i < 100000; ++$i){
  $val = $str[7];
}

平均実行時間(s)
パターン1 0.15074155330658
パターン2 0.05103883743286

約2.9倍
赤い彗星レベルなのですが、まず2バイトコードは含んでちゃだめなので使い道があまりなさそう。

これがC言語とかなら挙動的にポインタと納得できるがPHPだしなんか気持ち悪い^^;;;


文字列の置換はstr_replace()の方がpreg_replace()より速い

PHP
//パターン1
$str = "MizukiHachi";
for($i = 0; $i < 100000; ++$i){
  preg_replace('/Hachi/', 'Nana', $str);
}

//パターン2
$str = "MizukiHachi";
for($i = 0; $i < 100000; ++$i){
  str_replace('Hachi', 'Nana', $str);
}

平均実行時間(s)
パターン1 0.43134694099426
パターン2 0.20478415489197

約2.1倍
シンプルにstr_replaceで出来るなら、あえてpregを使う必要はないですね。


文字列を分解する時は、explode()よりpreg_split() ではなさそうだ…

PHP
//パターン1
$str = "水,樹,奈,々";
for($i = 0; $i < 100000; ++$i){
  $array = explode(',',$str);
}

//パターン2
$str = "水,樹,奈,々";
for($i = 0; $i < 100000; ++$i){
  $array = preg_split('/,/', $str);
}

preg_splitの方が圧倒的に遅かったんだが…
分解する文字列が短すぎたかもしれないので、必ずpreg_splitの方が遅い!
と言い切れないとは思うのだけど、正規表現で処理しているから遅いだろうなという印象どおり。


文字列が含まれているかはpreg_match()よりstrpos()

PHP
//パターン1
$val = "水樹奈々 ETERNAL BLAZE";
for($i = 0; $i < 100000; ++$i){
  if( strpos( $val, '奈々' ) !== false ){}
}

//パターン2
$val = "水樹奈々 ETERNAL BLAZE";
for($i = 0; $i < 100000; ++$i){
  if( preg_match( '/奈々/' , $val ) ){}
}

平均実行時間(s)
パターン3 0.12690899372101
パターン4 0.29959218502045

約2.4倍の高速化
正規表現系はやっぱ遅い。
正規表現が不要な場面では使わないにこしたことはないわけで、
決して正規表現使ってるんだぜ(`・ー・´)ドヤ!とかしてはいけない。


正規表現より関数

タイトルがざっくりすぎ^^;;;
色々判定関数が用意されていて

ctype_alnum  英数字判定
ctype_alpha  英字判定
ctype_cntrl  制御文字判定
ctype_digit  数字判定
ctype_graph  空白以外の印字可能文字判定
ctype_lower  小文字判定
ctype_print  印字可能文字判定 *空白含む
ctype_punct  空白及び英数字以外文字判定 *ざっくりいうと記号判定
ctype_space  空白文字判定 *空白文字なので空白以外にもタブとか改行とかも含まれる
ctype_upper  大文字判定
ctype_xdigit 16進数判定

これらとかで出来るなら関数使いましょう的な話。
is_xxxxというので必要な判定が出来るケースなら、そういうのも視野に入れる。

PHP
//パターン1
$val = "77";
for($i = 0; $i < 100000; ++$i){
  if( preg_match( '/^[0-9]+$/' , $val) ){}
}

//パターン2
$val = "77";
for($i = 0; $i < 100000; ++$i){
  if( ctype_digit($val) ){}
}

平均実行時間(s)
パターン1 0.30083012580872
パターン2 0.08202259540558

約3.7倍の高速化
ちょっぱや…


同じ値の複数変数への代入は分けて行う

PHP
//パターン1
$tmp1 = 0;
$tmp2 = 0;
for($i = 0; $i < 100000; ++$i){
  $tmp1 = $tmp2 = 1;
}

//パターン2
$tmp1 = 0;
$tmp2 = 0;
for($i = 0; $i < 100000; ++$i){
  $tmp1 = 1;
  $tmp2 = 1;
}

平均実行時間(s)
パターン1 0.04150550365448
パターン2 0.02898190021515

約1.4倍の高速化
これは想像より速かったし、代入の代入するより可読性は良いと思うので
こういう書き方した方がよいでしょう。


変数がbool型か調べる時は比較演算子を使う

PHP
//パターン1
$v = 1;
for($i = 0; $i < 100000; ++$i){
  if( is_bool( $v ) ){}
}

//パターン2
$v = 1;
for($i = 0; $i < 100000; ++$i){
  if( $v === true || $v === false ){}
}
平均実行時間(s)
パターン1 0.071662521362305
パターン2 0.039335107803345

約1.8倍
かなり速いが、ぱっと見何やってるの?って感じだし、
そんなにbool型かを判定することもないので
あえてやる必要はないでしょう。


配列が空かの判定はbool型にキャストした結果で判定 ではなさそうだ…

PHP
//パターン1
$array = [1,2,3,4,5,6,7];
for($i = 0; $i < 100000; ++$i){
  if( count($array) > 0 ){}
}

//パターン2
$array = [1,2,3,4,5,6,7];
for($i = 0; $i < 100000; ++$i){
  if( (bool)$array ){}
}

//パターン3
$array = [1,2,3,4,5,6,7];
for($i = 0; $i < 100000; ++$i){
  if( isset( $array[0] ) ){}
}

平均実行時間(s)
パターン1 0.09809997081757
パターン2 0.24926652908325
パターン3 0.03348140716553

bool型にキャストしたら激遅
ここまで真逆な結果が出ると何か別の要因がありそうですが、
何度やっても同じ傾向が見られるので遅いのだろう。

パターン3は速いのだけど、配列が空では無いときに必ず存在する事の
担保がとれているインデックスがロジック上ないといけない。
あと、まったく事情を知らない人がコードを見たときに、
指定したインデックスの中身の存在を確認しているロジックだと
誤認してしまう可能性もあるので、個人的にはあまりオススメしない。
せめてempty()を使う方が、関数の意味的にもマッチしている。
速度はisset()よりは、やや速い


配列に要素を追加する時は、$array[]で代入

PHP
//パターン1
$array = [];
for($i = 0; $i < 100000; ++$i){
  array_push($array, 'mizuki');
}

//パターン2
$array = [];
for($i = 0; $i < 100000; ++$i){
  $array[] = 'mizuki';
}

平均実行時間(s)
パターン1 0.23704917430878
パターン2 0.19576594829559

約1.2倍
パターン2の方がちょっと速い。
公式リファレンスにも1個加えるときはパターン2が良いよと書いてあるのでその通りなのだが、
array_push()は複数個を追加する時に使うという役割がメインだと思われるので
複数個の時は見やすくなるしarray_push()で良いのではなかろうか。


array_search()やin_array()使うならループして判定 ではなさそうだ…

PHP
//パターン1
$array = ['HiroseSuzu','MizukiNana','KamishiraishiMone','KimuraFumino'];
for($i = 0; $i < 100000; ++$i){
  if( array_search('MizukiNana', $array) ){}
}

//パターン2
$array = ['HiroseSuzu','MizukiNana','KamishiraishiMone','KimuraFumino'];
for($i = 0; $i < 100000; ++$i){
  if( in_array('MizukiNana', $array) ){}
}

//パターン3
$array = ['HiroseSuzu','MizukiNana','KamishiraishiMone','KimuraFumino'];
$tmpArray = array_flip($array);
for($i = 0; $i < 100000; ++$i){
  if( array_key_exists('MizukiNana', $tmpArray) ){}
}

//パターン4
$array = ['HiroseSuzu','MizukiNana','KamishiraishiMone','KimuraFumino'];
for($i = 0; $i < 100000; ++$i){
  foreach ($array as $value) {
    if($value == 'MizukiNana'){
      break;
    }
  }
}

平均実行時間(s)
パターン1 0.17810578346252
パターン2 0.17967174053192
パターン3 0.12337293624878
パターン4 0.12417731285095

ループの方が約1.4倍の高速化だけど…
なかなか計測値が収束しなかったが、概ねarray_search()とin_array()は同程度の早さで
パターン3とパターン4も同程度の早さ。

パターン3はflipしているので使えるケースは限定されるから
汎用的に対応でき速度もあるループが良さそうである。
、要素数とヒットさせたい要素の位置にもよるので
ヒットさせる文字列をKimuraFuminoで行ってみたら…
array_search()もin_array()も遅くなったが
ループも匹敵する以上に遅くなる結果に。

ヒットさせる値の位置がどれくらい分散するかで平均速度は変わるけど
要素が増えるとループはかなり遅くなりそうなので、
関数を使った方が実行速度をある程度読めるのでよさそう。


配列の中身を連結しての文字列化は、implode()よりjoin() ではなさそうだ…

PHP
//パターン1
$array = [1,2,3,4,5,6,7];
for($i = 0; $i < 100000; ++$i){
  $str = implode(',', $array);
}

//パターン2
$array = [1,2,3,4,5,6,7];
for($i = 0; $i < 100000; ++$i){
  $str = join(',', $array);
}

かならずしもどちらが速いわけではない
ローカルPC上なので、完全に同じ負荷状態になってないから
それを踏まえると、実行時間はほぼ同じと判断してよさそう。

そもそもjoinはimplodeのエイリアスなので差が出ない方が正しいはず。


画面出力はprint()ではなくechoを使う

PHP
//パターン1
for($i = 0; $i < 100000; ++$i){
  print '';
}

//パターン2
for($i = 0; $i < 100000; ++$i){
  echo '';
}

平均実行時間(s)
パターン1 0.028943252563477
パターン2 0.025534152984619

約1.1倍の高速化
そんなに変わらないなぁという印象なのと、
個人的にはechoばっかり使ってたので今まで通りでいいやという^^;;;


echoで複数文字列の画面出力の時は「.」ではなく「,」で繋ぐ

PHP
//パターン1
for($i = 0; $i < 100000; ++$i){
  echo '' . '';
}

//パターン2
for($i = 0; $i < 100000; ++$i){
  echo '' , '';
}

平均実行時間(s)
パターン1 0.04546332359314
パターン2 0.03141794204712

約1.4倍の高速化

結構差が出る。
よく echo "水樹奈々:年齢" . $age . "歳";みたいな感じで書くので
ちりつもで速くなりそう。


現在時刻を取得時は$_SERVER['REQUEST_TIME']を使う

PHP
//パターン1
for($i = 0; $i < 100000; ++$i){
  $now = time();
}

//パターン2
for($i = 0; $i < 100000; ++$i){
  $now = $_SERVER['REQUEST_TIME'];
}

平均実行時間(s)
パターン1 0.052512764930725
パターン2 0.049386715888977

ほぼ同じ
関数呼び出し方は若干遅いけど、そこまで差はないので
time()でかまわない気がする。


ダブルクォーテーションよりシングルクォーテーション

PHP
//パターン1
for($i = 0; $i < 100000; ++$i){
  $str = "MizukiNana";
}


//パターン2
for($i = 0; $i < 100000; ++$i){
  $str = 'MizukiNana';
}

ほぼ同じ
これぐらいシンプルな場合は特に気にするレベルではないが、
そもそも、ダブルとシングルで根本的に出来ることの違いがあるので
その辺を踏まえて検証してみると。

PHP
//パターン3
$val = "Mizuki";
for($i = 0; $i < 100000; ++$i){
  $str = "${val}Nana";
}

//パターン4
$val = 'Mizuki';
for($i = 0; $i < 100000; ++$i){
  $str = $val . 'Nana';
}

平均実行時間(s)
パターン3 0.06001923084259
パターン4 0.047677421569824

約1.26倍の高速化
ダブルクォーテーションは中身を解釈してゴニョゴニョしてくれる分遅くなる。
文字列と変数の連結を何個もする必要がある場合はパターン3の方が
可読性は良くなる感じもするが、こういうケースは実装物によっては
頻繁に出てくるのでパターン4で書いた方が全体高速化の寄与できると思われる。