君がッ for 文の条件式に関数を書かなくなるまで指摘をやめないッ!

はじめに

実務にて、多重 for 文の条件式に配列の要素数を数える為の count 関数が記述されているコードをよく見かける。これでは、条件式が評価される度に関数が実行されて、パフォーマンスの Overhead(オーバーヘッド)が発生する。本当に?どれぐらい?それを実際に検証した情報になります。

検証環境

解説

検証

検証する為に 1000 万個の要素をもつ配列を作成します。

$array = range(1, 10000000);

条件式に count 関数を記述した for 文を実行して、処理時間を計測します。

$time = microtime(true);

for ($i = 0; $i < count($array); ++$i);

echo 'time: ' . (microtime(true) - $time) . ' s'; // time: 1.0117928981781 s

平均、約 1.01 秒でした。

次は、count 関数の戻り値を変数に格納して for 文を実行、処理時間を計測します。

$time = microtime(true);

for ($i = 0, $count = count($array); $i < $count; ++$i);

echo 'time: ' . (microtime(true) - $time) . ' s'; // time: 0.74304008483887 s

平均、約 0.74 秒でした。

上記の検証結果より、Overhead(オーバーヘッド)により約 0.27 秒の差が発生することがわかりました。つまり、条件式に関数を書くと処理が遅くなります。

条件式が評価される度に関数が実行されているからですね。

「積羽舟を沈む」とは正にこの事で、小さな無駄の積み重なりが大きな問題に発展することがあるので注意です。

考察

そもそもなぜ、このようなコードを書いてしまうエンジニアがたくさんいるのか。

for 文の基本的な評価の流れを雰囲気で理解しているからなんじゃないかなーとか勝手に思っている。

ということで、for 文を理解する為の評価の流れを簡単に説明してみる。

for 文には初期化式条件式変化式という 3 つの式があります。

for (初期化式; 条件式; 変化式) {
  // 処理
}

初期化式は、for 文の開始時に一度だけ評価します。

条件式は、各繰り返しの前に評価します。式の値が true の場合は、brace(ブレース)内の処理を実行します。式の値が false の場合は、繰り返しを終了します。

変化式は、各繰り返しの後に評価します。

わかりやすいように Flowchart(フローチャート)にしました。

条件式になぜ count 関数を書いてはいけないのか、一目瞭然です。

わからない方は、矢印と処理を 1 つ 1 つゆっくりと声に出して辿ると理解できます、条件式が何度も評価されていることがわかると思います。

以上です。

おわりに

ついでに、count() について PHP 7.2 から下記のような仕様になったので注意。

警告 count() は、再帰を検出して無限ループを回避するようになっています。 しかしその場合 (配列の中に自分自身が複数回登場する場合) は毎回 E_WARNING を発行し、期待する結果より大きい数を返します。

https://www.php.net/manual/ja/function.count.php

4重 for 文の条件式に count() が直書きしてるようなプログラムの改修で、数千件の E_WARNING と数 GB(ギガバイト) のエラーログを見た時は絶望した。

二度とやりたくないわ。