少し前にプログラムを書く上での時間と労力を削減する方法についての記事を書きました。
社内で「為になる」というお言葉をいただけたというのと、先月新卒が入社したこともあり良いタイミングということで、今回はPHPでよく使用する記述の処理速度の比較と高速化について解説したいと思います。
計測方法は下記のコードで各処理を100万回実行するのにかかった時間を5回計測し、その平均時間を求めます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?php const MAX_TRY = 5; const MAX_COUNT = 1000000; $pattern01_total = 0; for( $try_i = 0; $try_i < MAX_TRY; ++$try_i ) { $time_start = microtime(true); for( $i = 0; $i < MAX_COUNT; ++$i ) { //各処理 } $time = microtime(true) - $time_start; $pattern01_total += $time; } echo 'パターン1の平均タイム : ' . $pattern01_total / MAX_TRY; ?> |
目次
比較演算子「==」と「===」の比較
まずは、比較演算子の「==」と「===」の比較です。
比較は以下のように行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 | <?php $input = '789'; //パターン1 if( $input == '123' ) {} else if( $input == '789' ) {} //パターン2 if( $input === '123' ) {} else if( $input === '789' ) {} ?> |
結果はパターン1「==」のときの平均タイムが0.041917371749878秒、
パターン2「===」のときの平均タイムが0.018864583969116秒という結果になりました。
「==」と比べ、「===」のほうが、半分以下の処理速度となり明らかに速いことが分かります。
これは、「==」はデータ型を考慮しない多数のパターンで判定するため処理が遅くなるのに対し、「===」はデータ型まで完全に一致するかどうかで判定するため、確認するパターンが少なくなり処理速度が速くなるということです。
「echo」と「print」の比較
続いて、「echo」と「print」の比較です。
比較は以下のように行います。
1 2 3 4 5 6 7 8 9 | <?php //パターン1 echo ''; //パターン2 print ''; ?> |
結果はパターン1「echo」のときの平均タイムが0.075080013275146秒、
パターン2「print」のときの平均タイムが0.074736595153809秒という結果になりました。
printのほうが約0.0003秒速いという結果になりましたが、恐らくこれはごく僅かな差のため、速度面では差が無いと考えて問題ないかと思います。
ただ、実はprintよりもechoのほうが良いとされています。
これはある動作の違いにあります。
printとechoは出力するという点では一致していますが、戻り値に違いがあります。
echoでは戻り値がありませんが、printでは出力する中身に限らず必ず「1」が戻り値として返されます。
そのため、出力するだけであれば戻り値が不要なため、printよりもechoのほうが効率がよいとされています。
配列への要素追加の比較
続いて、「array_push()」と「$array[] =」の比較です。
比較は以下のように行います。
1 2 3 4 5 6 7 | //パターン1 array_push( $array, 'a' ); //パターン2 $array[] = 'a'; |
結果はパターン1「array_push( $array, ‘a’ );」のときの平均タイムが0.062974643707275秒、
パターン2「$array[] = ‘a’;」のときの平均タイムが0.049524641036987秒と、パターン2「$array[] = ‘a’;」のほうが1.2倍ほど速いことが分かります。
これは、関数と言語構造の違いによるものです。
言語構造とは、演算子や制御構造、関数定義など、プログラムを構成する基本的な要素のことで、言語仕様に直接組み込まれているため、処理が速くなる傾向があります。
一方、関数は呼び出し毎にコンパイラがコードを生成するため、オーバーヘッドが発生し実行速度が遅くなります。
要は簡単にいうと、言語構造は言語自体の構文で処理されるのに対して、
関数は変数や外部ファイル等を参照する必要があるため遅くなるということです。
そのため、関数を使わずに済むのであれば使わないようにしましょう。
条件分岐の順番
続いては条件分岐の順番による速度比較です。
今回はswitch文で頻度が低い順に並べたパターン1と頻度が高い順に並べたパターン2で比較します。
比較は以下のように行います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | <?php $frequency = 'high'; //パターン1 switch ($frequency) { case 'very low' : break; case 'low' : break; case 'above average' : break; case 'average' : break; case 'below average' : break; case 'high' : break; case 'very high' : break; default: break; } //パターン2 switch ($frequency) { case 'very high' : break; case 'high' : break; case 'above average' : break; case 'average' : break; case 'below average' : break; case 'low' : break; case 'very low' : break; default: break; } ?> |
結果はパターン1「低パターン順」のときの平均タイムが0.019187355041504秒、
パターン2「高パターン順」のときの平均タイムが0.01762957572937秒と、パターン2のほうが多少速いことがわかります。
これは単純に一致するかどうかを上から順番に確認するため、
頻度が高いものは上にすることで無駄に探索する必要がなくなり、結果的に速くなるということですね。
これはif文でも同じようなことがいえますので、条件分岐はできるだけ高い頻度順に並べるようにしましょう。
ただ、処理速度重視で並び変えると可読性が悪くなることもあるので、注意が必要です。
繰り返し処理
続いて、繰り返し処理の中で行う必要がない処理を中で行った場合と外で行った場合の速度を比較します。
今回は1が入った100個の要素を持つ配列を用意し、要素数回for文を回す処理を行います。
比較は以下のように行います。
1 2 3 4 5 6 7 8 9 10 11 12 | <?php $array = array_fill(0, 100, 1); //パターン1 for( $i = 0; $i < count($array); ++$i ) {} //パターン2 $max = count($array); for( $i = 0; $i < $max; ++$i ) {} ?> |
結果はパターン1「繰り返し処理内」で行ったときの平均タイムが11.277407455444秒、
パターン2「繰り返し処理外」で行ったときのときの平均タイムが6.3062798023224秒と倍近くの差が出たことが分かります。
これは当たり前のことかと思いますが、繰り返し処理の中で処理する必要のないものは全て繰り返しから出して、外で行いましょうということです。
プレフィックス演算子とサフィックス演算子の比較
続いてはプレフィックス演算子とサフィックス演算子の比較です。
繰り返し処理でよく、$i++という書き方をするかと思いますがこれはサフィックス演算子による書き方で、もう一つ++$iと書くプレフィックス演算子があります。
このプレフィックス演算子とサフィックス演算子の比較を以下のように行ってみます。
1 2 3 4 5 6 7 8 9 10 | <?php //パターン1 for( $i = 0; $i < 100; $i++ ) {} //パターン2 $max = count($array); for( $i = 0; $i < 100; ++$i ) {} ?> |
結果はパターン1「サフィックス演算子」で行ったときの平均タイムが6.08397564888秒、
パターン2「プレフィックス演算子」で行ったときのときの平均タイムが6.0055746078491秒とごくわずかですが差がありました。
この差は処理方法の違いがあると思われます。
プレフィックス演算子は変数に対して直接演算を行い、その後に変数の値を返しますが、
サフィックス演算子は変数の値を返した後に演算を行うため、変数の値を一度返す必要があり、少し遅くなることがあります。
文字列の結合
次に文字と変数の値を合わせるときの比較です。
文字と変数の値を合わせる方法は結合する方法と文字列の中で変数を展開する方法の2パターンがあり、以下のように比較します。
1 2 3 4 5 6 7 8 9 10 11 | <?php $name = "BRISK"; //パターン1 $text = "株式会社 " . $name; //パターン2 $text = "株式会社 $name"; ?> |
結果はパターン1の文字列と変数の結合のときの平均タイムが0.68875594139099秒、
パターン2の文字列の中で変数を展開するときの平均タイムが0.55604586601257秒と、1.2倍ほど速いことが分かりました。
これは変数展開の場合は文字列内で変数の値を展開するだけですが、
結合の場合だと結合の結果を保存するためのメモリ領域が生成されるため、遅くなると思われます。
結合する文字列が長くなればなるほど、確保されるメモリ領域が増え、さらに処理速度も遅くなります。
文字列の置換
続いては文字列の置換方法の比較です。
preg_replaceとstr_replaceを用いた方法の2パターンを下記のように比較します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?php $text = 'My name is Yamada Taro.'; //パターン1 for( $i = 0; $i < MAX_COUNT; ++$i ) { preg_replace('/Yamada/', 'Hamada', $text); } //パターン2 for( $i = 0; $i < MAX_COUNT; ++$i ) { str_replace('Yamada', 'Hamada', $text); } ?> |
結果はパターン1の「preg_replace」による置換のときの平均タイムが0.12088236808777秒、
パターン2の「str_replace」のときの平均タイムが0.062667608261108秒と、倍近くの差が出ました。
これはstr_replaceは単純な文字列置換であるのに対し、preg_replaceは正規表現を用いるため、遅くなります。
というのも、正規表現はとても複雑なアルゴリズムが用いられており、多くの処理が行われるため遅くなります。
そのため、置換に限らずですが、正規表現を用いずに済む場合には用いないようにしましょう。
まとめ
ということでいかがだったでしょうか。
今回はPHPでよく使用する記述の処理速度の比較と高速化について解説しました。
「塵も積もれば山となる」ということわざがあるように、一つひとつの処理は小さな差であっても、それが積み重なれば処理速度が長くなります。
処理速度が長くなるということは、それだけ裏で処理をしているということですので、システムの安定性にも影響与えてしまいます。
また、処理速度はユーザーエクスペリエンスにも大きな影響を与えます。
処理速度が遅ければ遅いほど、ユーザーが離脱する可能性が高くなり、SEO面でも不利になることがあります。
したがって、プログラムを作成する際には、処理速度も重視し、より優れたユーザーエクスペリエンスを提供できるよう努めましょう。
以上、最後までご覧いただきありがとうございました~。