Edited at

PHP7になって三項演算子はifより速くなった→間違ってました、やっぱり三項演算子が(体感できないレベルで)遅いです

More than 1 year has passed since last update.

※ 2/2 コードに誤りがあったため記事修正・追記しています。

※ 2/3 さらに追記あり。若干テーマとは違いますが……。

修正リクエストやコメントでいただいたとおり、改行コード結合の記述漏れがあり、評価内容に誤りがありました

修正のうえ実行したところ、まったく違う結果が出てしまいました

昨夜の環境と異なるため、念のため、本日帰宅後にもう一度テスト計測しようと思います。

誤った情報となり、まことに申し訳ありません。


はじめに

可読範囲を上げるため真偽判定の代入とかでガンガン三項演算子を使う派だったのだけど、会社の人に「三項演算子の方がifで書くよりより遅いらしいっすよ」と言われた。

少しだけ調べてみたところ「昔は遅かったけれど、今はifと同等程度まで改善している」という記事がいくつか出てきた。

ただ、いずれもPHP5.xの情報。

PHP7.xになってからどうなったかの情報が知りたい。

調べてみたが見つからないので、自分で試してみた。

手元環境はOSX 10.12.3、PHP 7.0.14。


テストコード

<?php

$end_loop = 5000000;
$try_count = 10;

for ($j = 1; $j <= 10; $j++) {

// pattern1: if
$x = 0;
$startTime = microtime(true);
for ($i=0; $i <= $end_loop; $i++) {
if ($i % 2 === 0) {
$x = $x + $i . "\r\n";
} else {
$x = $x - $i . "\r\n";
}
}
$endtime1[$j] = microtime(true) - $startTime;

// pattern2: 三項演算子
$x = 0;
$startTime = microtime(true);
for ($i=0; $i <= $end_loop; $i++) {
$x = $i % 2 === 0 ? $x + $i . "\r\n" : $x - $i . "\r\n";
}
$endtime2[$j] = microtime(true) - $startTime;

}

$endtime1_total = 0;
$endtime2_total = 0;
for ($j = 1; $j <= $try_count; $j++)
{
$endtime1_total += $endtime1[$j];
$endtime2_total += $endtime2[$j];
echo <<<EOT
$j 回目 -----------
pattern1: {$endtime1[$j]}
pattern2: {$endtime2[$j]}

EOT;
}

$endtime1_ave = $endtime1_total / $try_count;
$endtime2_ave = $endtime2_total / $try_count;

echo <<<EOT
平均 -------------
pattern1: $endtime1_ave
pattern2: $endtime2_ave

EOT;


結果


結果は以下の通り。
5,000,000回というループなので実際のコード上では誤差のレベルかもしれないけど、明らかに三項演算子を使った場合の方が速くなっている。
手元にPHP5.xの環境がないので試していないが、少なくともPHP7に限って言えば三項演算子は使っても特に問題なさそう。

ごめんなさい、間違えてました!


2017.2.2 12:31 追記・再検証

冒頭に追加した通り、コードに誤りがあり、修正の指摘をいただきました。

最初に掲載したのは、不当な評価によるテスト結果でした!

当初のコードではifに改行コード追加、三綱演算子に改行コード追加なしとなっており、評価方法が一致していませんでした。

そこで、修正コードにて、取り急ぎ再検証を実施。

昨夜と違う会社の環境(Win8.1 + PHP7.0.10)になりますが、以下の結果になりました。

若干、本当に若干、気持ち三項演算子が遅いレベル。

5,000,000ループでこのコンマ二ケタ未満の差なので、PHP5.xと同じく速度は同等としてよいかと思います。

1 回目 -----------

pattern1: 0.69710183143616
pattern2: 0.70417594909668
2 回目 -----------
pattern1: 0.69302010536194
pattern2: 0.70412611961365
3 回目 -----------
pattern1: 0.69366383552551
pattern2: 0.70402693748474
4 回目 -----------
pattern1: 0.69302415847778
pattern2: 0.70490288734436
5 回目 -----------
pattern1: 0.69333600997925
pattern2: 0.70488405227661
6 回目 -----------
pattern1: 0.69333410263062
pattern2: 0.70417189598083
7 回目 -----------
pattern1: 0.69367694854736
pattern2: 0.7043468952179
8 回目 -----------
pattern1: 0.69339680671692
pattern2: 0.70407390594482
9 回目 -----------
pattern1: 0.69336700439453
pattern2: 0.70391917228699
10 回目 -----------
pattern1: 0.69343590736389
pattern2: 0.70438694953918
平均 -------------
pattern1: 0.6937356710434
pattern2: 0.70430147647858

こちらの確認不足で不正確な記事を公開しましたこと、深くお詫び申し上げます。

また、記事修正リクエストおよびコメントでのご指摘ありがとうございました。


2017.2.3 3:57追記

「そもそも\r\nの追記でキャストしてることによりさらに遅くなってるのでは」という指摘をいただき、全くその通りだと思い、コード修正。

(そもそもヒアドキュメントで改行も入力しているのに、なぜ自宅Macで試してるのに\r\nを入れていたのか全く思い出せない……)

ということで、\r\nとの文字列結合を除去し、もう一度テスト。

とは言え、評価内容としてはどちらも同等なはずなので、実行速度こそ変われど三項演算子のほうが遅くなるはず。


before

<?php

$end_loop = 5000000;
$try_count = 10;

for ($j = 1; $j <= 10; $j++) {

// pattern1: if
$x = 0;
$startTime = microtime(true);
for ($i=0; $i <= $end_loop; $i++) {
if ($i % 2 === 0) {
$x = $x + $i;
} else {
$x = $x - $i;
}
}
$endtime1[$j] = microtime(true) - $startTime;

// pattern2: 三項演算子
$x = 0;
$startTime = microtime(true);
for ($i=0; $i <= $end_loop; $i++) {
$x = $i % 2 === 0 ? $x + $i : $x - $i;
}
$endtime2[$j] = microtime(true) - $startTime;

}

$endtime1_total = 0;
$endtime2_total = 0;
for ($j = 1; $j <= $try_count; $j++)
{
$endtime1_total += $endtime1[$j];
$endtime2_total += $endtime2[$j];
echo <<<EOT
$j 回目 -----------
pattern1: {$endtime1[$j]}
pattern2: {$endtime2[$j]}

EOT;
}

$endtime1_ave = $endtime1_total / $try_count;
$endtime2_ave = $endtime2_total / $try_count;

echo <<<EOT
平均 -------------
pattern1: $endtime1_ave
pattern2: $endtime2_ave

EOT;


結果は以下の通り。

1 回目 -----------

pattern1: 0.22407507896423
pattern2: 0.26336312294006
2 回目 -----------
pattern1: 0.20793604850769
pattern2: 0.26573705673218
3 回目 -----------
pattern1: 0.21822309494019
pattern2: 0.26395297050476
4 回目 -----------
pattern1: 0.22034907341003
pattern2: 0.26142692565918
5 回目 -----------
pattern1: 0.21319198608398
pattern2: 0.25592613220215
6 回目 -----------
pattern1: 0.21849298477173
pattern2: 0.26207113265991
7 回目 -----------
pattern1: 0.21364402770996
pattern2: 0.26462888717651
8 回目 -----------
pattern1: 0.21056199073792
pattern2: 0.26701211929321
9 回目 -----------
pattern1: 0.2266788482666
pattern2: 0.28541398048401
10 回目 -----------
pattern1: 0.23357200622559
pattern2: 0.27436709403992
平均 -------------
pattern1: 0.21867251396179
pattern2: 0.26638994216919

予想通り……というかご指摘通りなのだけど、stringへのキャストがなくなった分、半分以下の時間短縮になっている。

今回のテストは5,000,000回というループで、それでも三項演算子による速度低下は平均で0.05秒程度。

アプリにもよるけれど、誤差の範囲と考え、可読性でどちらを採用するか決めて問題ないと思う。

むしろ、三項演算子よりも暗黙の型変換のほうが、速度低下が大きいということを常に念頭に入れて書いたほうがよい、という当たり前の結論になってしまった。

なるほどいい勉強になりました。