【PHP】処理時間を正確に計測する方法

【PHP】処理時間を正確に計測する方法

簡易版

以下、ネットで「PHP 処理時間 計測」で検索するとよく出てくるコードです。

$start = microtime(true);
sleep(1);
$stop = microtime(true);
echo $stop - $start . 'sec'; // 1.0001630783081sec

上記のコードはmicrotime関数を使って処理後の時間から処理前の時間を引いて差分を出しています。

microtime関数は現在のUnixタイムスタンプをマイクロ秒まで返す関数で、引数にtrueを渡すとfloat型で14桁、引数に何も指定しないとstring型で少数6桁、整数10桁の計16桁取得できます。

var_dump(microtime(true)); // float(1527337914.6467)
var_dump(microtime()); // string(21) "0.64666000 1527337914"

ざっくり測る分にはこれでいいのかもしれませんが、このコードの出力結果は少々気持ち悪いです。
microtime関数はマイクロ秒を返す関数で、マイクロ秒は100万分の1秒ですので少数は6桁までのはずです。
ですが、上記コードでは1.0001630783081secと少数が13桁まで表示されてしまいました。

調べてみたところPHPでは小数点の計算に関して精度が保証されていないようです。

浮動小数点数の精度は有限です。…
小数の最後の桁を信用してはいけませんし、 小数を直接比較して等しいかどうかを調べてはいけません。

検証してみたところ、少数を含んだ桁数の多い数字の計算は精度が良くないようです。。。
ですので、正確に測る場合はmicrotime関数を引数なしで実行し、整数と小数部分を別々に計算するのが良さそうです。

小数点の計算をPHPで行うにはBC Math関数を使うと良いそうですが、今回はこれを使わずに計算する方法を考えてみたいと思います。
※BC Math関数を使うには別途インストールが必要になります。

次に、もう少し処理時間の短いコードを書いて見ましょう。

$start = microtime(true);
foreach(range(1, 100) as $i) {
    ++$i;
}
$stop = microtime(true);
echo $stop - $start . 'sec'; // 7.1525573730469E-6sec

結果は7.1525573730469E-6secと桁数が多すぎるため指数表記となってしまいました。
ですので、この場合はsprintfやprintf、number_format関数などを使用して整形するのが良さそうです。

以上の点を踏まえて処理時間を正確に計測するクラス、関数を作ってみました。

完成版

class Execution_Time {
    
    private $start_mt;
    private $stop_mt;
    
    // インスタンス生成時に呼ばれる
    public function __construct() {
        // 計測開始時のタイムスタンプを整数と少数に分けて配列にブチ込む
        $this->start_mt = explode(' ', microtime());
    }
    
    // インスタンス破棄時に呼ばれる
    public function __destruct() {
        // 計測終了時のタイムスタンプを整数と少数に分けて配列にブチ込む
        $this->stop_mt = explode(' ', microtime());
        // 整数同士を計算
        $diff_sec = (float) $this->stop_mt[1] - (float) $this->start_mt[1];
        // 少数同士を計算
        $diff_usec = (float) $this->stop_mt[0] - (float) $this->start_mt[0];
        // 整形
        $execution_time = sprintf('%.6f', $diff_sec + $diff_usec);
        // 最後に0がついていたら削除
        $execution_time = preg_replace('/0*$/', '', $execution_time);
        // 計測時間を出力
        echo $execution_time . 'sec';
    }
}
$et = null;
function start_measure() {
    global $et;
    // インスタンスを生成する
    $et = new Execution_Time();   
}

function stop_measure() {
    global $et;
    // インスタンスを破棄する
    $et = null;
}

// 計測スタート
start_measure();

// 計測したい処理をここに書く。
sleep(1);

// 計測終了
stop_measure(); // 1.000168sec

実際に使う場合は以下のようにクラスと関数部分を別ファイルにしておくと使い勝手が良さそうです。

execution-time.php
class Execution_Time {
    
    private $start_mt;
    private $stop_mt;
    
    // インスタンス生成時に呼ばれる
    public function __construct() {
        // 計測開始時のタイムスタンプを整数と少数に分けて配列にブチ込む
        $this->start_mt = explode(' ', microtime());
    }
    
    // インスタンス破棄時に呼ばれる
    public function __destruct() {
        // 計測終了時のタイムスタンプを整数と少数に分けて配列にブチ込む
        $this->stop_mt = explode(' ', microtime());
        // 整数同士を計算
        $diff_sec = (float) $this->stop_mt[1] - (float) $this->start_mt[1];
        // 少数同士を計算
        $diff_usec = (float) $this->stop_mt[0] - (float) $this->start_mt[0];
        // 整形
        $execution_time = sprintf('%.6f', $diff_sec + $diff_usec);
        // 最後に0がついていたら削除
        $execution_time = preg_replace('/0*$/', '', $execution_time);
        // 計測時間を出力
        echo $execution_time . 'sec';
    }
}
$et = null;
function start_measure() {
    global $et;
    // インスタンスを生成する
    $et = new Execution_Time();   
}

function stop_measure() {
    global $et;
    // インスタンスを破棄する
    $et = null;
}
index.php
require './execution-time.php';

// 計測スタート
start_measure();

// 計測したい処理をここに書く。
sleep(1);

// 計測終了
stop_measure(); // 1.000168sec