PHPで性能を検証するパフォーマンス測定コード

PHPで性能を検証するパフォーマンス測定コード
PHPで性能を検証するパフォーマンス測定コード

 

 
読了目安:222

Are you want to translate this page to English? Please click this link to translate via ‘©Google Translate'!

ganohr.net favicon

皆さんこんにちは、ガノー(Twitter:ganohr)です!

PHPのプログラミングにおいて、パフォーマンスチューニングをするときがあります。

その際に活用できる、実際の処理時間と使用メモリ料を計測するコードを共有します。

更新履歴
2023/05/15 ganohrs_check_performanceを更新:計測した時間を一応返却するように修正
2023/05/14 公開

PHPでパフォーマンス計測を行うコード

<?php
function ganohrs_check_performance($func, $max = 1000000, $mes = "") {
    echo "=== check performance $mes start ===" . PHP_EOL;
    $t = 0;
    if (function_exists("hrtime")) {
        $t = hrtime(true);
    } elseif (function_exists("microtime")) {
        $t = microtime(true);
    } else {
        throw Exception("No Function for Time Measurement!");
    }
    memory_reset_peak_usage();
    ganohrs_dump_memory();
    for($i = 0; $i < $max; $i++) {
        $func();
    }
    $p = 0;
    if (function_exists("hrtime")) {
        $p = (hrtime(true) - $t) / 1e+9;
    } elseif (function_exists("microtime")) {
        $p = (microtime(true) - $t);
    } else {
        $p = (time() - $p);
    }
    echo "performance: " . $p . PHP_EOL;
    ganohrs_dump_memory();
    echo "=== check performance $mes end   ===" . PHP_EOL . PHP_EOL;
    return $p;
}

function ganohrs_dump_memory() {
    print "memory usage      : ". (round(memory_get_usage     (    ) / (2 ** 20) * 1000) / 1000) ."MB" . PHP_EOL;
    print "memory usage real : ". (round(memory_get_usage     (true) / (2 ** 20) * 1000) / 1000) ."MB" . PHP_EOL;
    print "memory peak       : ". (round(memory_get_peak_usage(    ) / (2 ** 20) * 1000) / 1000) ."MB" . PHP_EOL;
    print "memory peak  real : ". (round(memory_get_peak_usage(true) / (2 ** 20) * 1000) / 1000) ."MB" . PHP_EOL;
}

PHPでパフォーマンス計測を実際に行うサンプルコード

<?php
ganohrs_check_performance(function() {
    $v = eval("return 2**20;");
});

ganohrs_check_performance(function() { $v = eval("return pow(2, 20);"); });
出力例
=== check performance  start ===
memory usage : 0.39MB
memory usage real : 2MB
memory peak : 0.39MB
memory peak real : 2MB
performance: 1.1194066
memory usage : 0.391MB
memory usage real : 2MB
memory peak : 0.425MB
memory peak real : 2MB
=== check performance end ===

=== check performance start ===
memory usage : 0.391MB
memory usage real : 2MB
memory peak : 0.391MB
memory peak real : 2MB
performance: 1.6728114
memory usage : 0.391MB
memory usage real : 2MB
memory peak : 0.425MB
memory peak real : 2MB
=== check performance end ===

これは、phpでべき乗算を行うためのpow(n, m)関数と、べき乗演算子n ** meval("~")で呼び出した際の、パフォーマンスの差を計測しています(Win 10 64bit、Ver 8.2.4)。

構文解析が間に挟まる場合、pow関数を用いるよりも、**演算子を利用するほうが顕著にパフォーマンスに優れることがわかります。

 

なお、これらをevalではなく直接記述しても、やはり**演算子を用いる方がパフォーマンス的には優位です。

<?php
ganohrs_check_performance(function(){
    $v = 2**20;
});

ganohrs_check_performance(function(){ $v = pow(2, 20); });
出力例
=== check performance  start ===
memory usage : 0.39MB
memory usage real : 2MB
memory peak : 0.39MB
memory peak real : 2MB
performance: 0.062826
memory usage : 0.39MB
memory usage real : 2MB
memory peak : 0.39MB
memory peak real : 2MB
=== check performance end ===

=== check performance start ===
memory usage : 0.39MB
memory usage real : 2MB
memory peak : 0.39MB
memory peak real : 2MB
performance: 0.0807358
memory usage : 0.39MB
memory usage real : 2MB
memory peak : 0.39MB
memory peak real : 2MB
=== check performance end ===

とはいえ、evalを挟む場合とくらべ、速度にはさほど違いがないことも分かります。

このように、パフォーマンスチューニングにおいては、実際の処理時間やメモリ使用量を元に判断していく必要があります。

PHPのパフォーマンス計測に用いる関数の解説

上記のコードで利用しているPHPの関数群を見ていきましょう。

PHPでパフォーマンス計測:メモリ使用量を取得するmemory_get_usage関数

サムネイルPHP: memory_get_usage - Manual ... 外部サイトへアクセスwww.php.net

memory_get_usageは、現在のプロセスが利用しているメモリ使用量を取得します。

引数を与えなかったり、falseを与えると、PHPが内部で利用しているメモリ使用量を出力します。

 

逆にtrueを与えた場合、PHPが実際に割り当てているメモリ使用量を出力します。

PHPでパフォーマンス計測:ピークメモリ使用量を取得するmemory_get_peak_usage関数

サムネイルPHP: memory_get_peak_usage - Manual ... 外部サイトへアクセスwww.php.net

memory_get_peak_usageは、現在のプロセスが利用した最大メモリ使用量を取得します。

引数を与えなかったり、falseを与えると、PHPが内部で利用しているメモリ使用量を出力します。

 

逆にtrueを与えた場合、PHPが実際に割り当てているメモリ使用量を出力します。

なお、パフォーマンス計測においては、次のmemory_reset_peak_usage関数を併用する必要があります。

PHPでピークメモリ使用量の値をリセットするmemory_reset_peak_usage関数

サムネイルPHP: memory_reset_peak_usage - Manual ... 外部サイトへアクセスwww.php.net

memory_reset_peak_usageは、現在のプロセスが利用した最大メモリ使用量をリセットします。

パフォーマンス計測においては、任意の区間のパフォーマンスを計測する必要があります。

したがって、パフォーマンス計測を開始する際に、memory_reset_peak_usage関数を用いて予めリセットせねばなりません。

PHPでパフォーマンス計測:可能な限り高精度に現在時刻を取得するhrtime関数

サムネイルPHP: hrtime - Manual ... 外部サイトへアクセスwww.php.net

hrtime関数は、比較的新しい関数であり、7.3以降が対象になります。OS搭載の高精度な時間取得APIを用いて現在の時刻をマイクロ秒精度で取得します。

第一引数を省略するか、第一引数にfalseを指定すると、戻り値は"現在のエポックタイムからの経過秒数" . " " . "現在の秒数から経過したマイクロ秒数"という文字列を返却します。

 

パフォーマンス計測においては、これでは都合が悪いため第一引数にtrueを指定して、マイクロ秒精度の値を示すfloat値を取得します。

なお、このマイクロ秒を整数部をミリ秒、小数部をマイクロ秒にする場合は、hrtime(true) / 1e6とし、整数部を秒、小数部をマイクロ秒含めた時間とするにはhrtime(true) / 1e9とします。

 

なお、hrtime関数はWindows版のPHPではバンドルされていますが、それ以外のOSではデフォルトではバンドルされていません。

 

LinuxベースのOSなどでは、PECL拡張モジュールから追加でインストールせねばなりません。

複数のOSで利用されうるパフォーマンス計測コードにおいては、function_existsを用いて、予めhrtime関数があるかどうか確認しておく必要があります。

サムネイルPHP: HRTime - Manual ... 外部サイトへアクセスwww.php.net

 

またhrtime関数を利用する場合、次節に掲載するmicrotime関数との互換性の兼ね合いでhrtime(true) / 1e9という変換をすることを推奨します。

PHPでパフォーマンス計測:マイクロ秒精度で現在時刻を取得するmicrotime関数

サムネイルPHP: microtime - Manual ... 外部サイトへアクセスwww.php.net

前述のhrtimeは比較的新しいため、まだまだ利用できる環境は多くありません。そのため多くの場合、代替としてmicrotime関数が利用されます。この関数はPHP4以降標準搭載されています。

 

第一引数を省略するか、第一引数にfalseを指定すると、戻り値は"現在の秒数からの経過マイクロ秒数" . " " . "エポックタイムから経過した現在の秒数"という形式のstring値を返却します。

パフォーマンス計測においては、これでは都合が悪いため第一引数にtrueを指定して、整数部がエポックタイムからの経過秒数、小数部をマイクロ秒精度の値としたfloat値を取得します。

 

なお、trueを指定した場合、前述のhrtime(true) / 1e9の結果と同じ形式で値を処理できます。

PHPでパフォーマンス計測:実行時間の求め方は終了時刻-開始時刻

※ 再掲、先のコード例と同じ

<?php
function ganohrs_check_performance($func, $max = 1000000, $mes = "") {
    echo "=== check performance $mes start ===" . PHP_EOL;
    $t = 0;
    if (function_exists("hrtime")) {
        $t = hrtime(true);
    } elseif (function_exists("microtime")) {
        $t = microtime(true);
    } else {
        throw Exception("No Function for Time Measurement!");
    }
    memory_reset_peak_usage();
    ganohrs_dump_memory();
    for($i = 0; $i < $max; $i++) {
        $func();
    }
    ganohrs_dump_memory();
    $p = 0;
    if (function_exists("hrtime")) {
        $p = (hrtime(true) - $t) / 1e+9;
    } elseif (function_exists("microtime")) {
        $p = (microtime(true) - $t);
    } else {
        $p = (time() - $p);
    }
    echo "performance: " . $p . PHP_EOL;
    echo "=== check performance $mes end   ===" . PHP_EOL . PHP_EOL;
    return $p;
}

先のコード例では、$tが開始時刻、$pが終了時刻と開始時刻の差(=実行時間)を計測しています。

このとき、hrtime関数ととmicrotime関数にtrueを渡すことと、hrtime関数は値 / 1e+9としてmicrotime関数の戻り値と互換性を確保しておくことが味噌です。

 

とはいえ、これ以上の説明は不要でしょう。

PHPの時間取得関数hrtimeとmicrotimeの性能差はあるのか?

さて、hrtime関数とmicrotime関数を見てきましたが、実際のところどれぐらい精度に差があるのでしょうか?

計測用に下記のコードを実行しました。

<?php
$p_arr = [];
for ($j = 0; $j <= 1; $j++) {
    $p[$j] = [];
    $sum1 = 0;
    $sum2 = 0;
    $sub1 = 0;
    $sub2 = 0;
    for ($i = 0; $i < 100; $i++) {
        $check_func = $j === 0 ? "ganohrs_check_performance_hrtime" : "ganohrs_check_performance_microtime";
        $p1 = call_user_func($check_func, function(){
            $v = eval("return 2**20;");
        }, 1000000, "$j : $i : 0");
        $sum1 += $p1;

$p2 = call_user_func($check_func, function(){ c }, 1000000, "$j : $i : 1"); $sum2 += $p2;
$p[$j][$i] = [$p1, $p2]; } $ave1 = $sum1 * 0.1; $ave2 = $sum2 * 0.1; for ($i = 0; $i < 100; $i++) { $sub1 += abs($ave1 - $p[$j][$i][0]); $sub2 += abs($ave2 - $p[$j][$i][1]); } echo "Average1: $ave1, $sub1" . PHP_EOL; echo "Average2: $ave2, $sub2" . PHP_EOL; }

このコードは、少し早い$v = eval("return 2**20;");$v = eval("return pow(2, 20);");を呼び出します。

また、それぞれ100万回呼び出すのを1回として、それを100回呼び出し、平均実行速度と、それぞれ100回分の実行速度の差の絶対値を加算して比較する処理を行っています。

 

要は1億回の計測を行い「どの程度平均実行速度とのばらつきがでるのか」を調べています。

実際の計測結果は以下のとおりです。

出力例
Average1: 1.13567593     , 0.016423698
Average2: 1.703622412 , 0.03369031544
Average1: 1.1413952374458, 0.020074790716171
Average2: 1.6897796916962, 0.025672821998596

※ 見やすくするため半角スペースを追加済み
※ 最初のAverage1hrtimeを利用して比較的早い関数の処理の計測結果
※ 最初のAverage2hrtimeを利用して比較的遅い関数の処理の計測結果
※ つぎのAverage1microtimeを利用して比較的早い関数の処理の計測結果
※ つぎのAverage2microtimeを利用して比較的遅い関数の処理の計測結果

 

その結果を見ると、別段hrtimemicrotimeもどっちもどっちです。

よくパフォーマンス計測ではOSの状況などに左右されがちですが、

  • 他のアプリをすべて終了して計測している
  • 100回の平均値とその差の平均を取っており、誤差はほとんど無視できる
  • hrtimemicrotimeも誤差の比率等に規則性がみあたらない

という条件&結果ですので、わざわざhrtimeを利用する利点はほとんど存在しないといっても良いでしょう。

 

ひとまずこれまでmicrotimeを利用してきた方はそのまま継続して利用し、今後hrtimeの精度が向上していこう、改めて利用すれば良いと思います。

最後に

パフォーマンス計測に誤差は付きものです。

そのため、誤差が少ないとされるhrtimeを使いたいと考えるのは至極当然です。

されど、実際に誤差を検証してみて、結局これまで同様誤差があることがわかりました。

 

ついつい理論上の話や、すでにある情報を元に意思決定しがちですが、パフォーマンス計測を実際に行ったり、誤差計測を行った上で判断すべきというのが、今回の検証で判明したことは重要です。

みなさんも今回共有したコードを活用して、パフォーマンス計測を行っていただければ幸いです。

以上、ガノー(Twitter:ganohr)でした!

WordPressの不具合対応/カスタマイズ¥15,000~

PC歴25年超、SE歴10年超、WordPress運営歴7年超、WordPressエンジニア歴5年超のスキルとノウハウを提供します

当サイト管理人の「ガノー」(Ganohr)は、日本最大手且つ東証一部上場企業が運営するクラウドソーシングサイト『Lancers』にて、認定ランサーとして活動しています。


※ 認定ランサーとはLancersにより様々な能力 ( 高い仕事遂行率・高い顧客満足度・多くの実績、など ) を評価したプロフェッショナルを認定する制度です。

 

PHPカテゴリの最新記事