「@」でエラー抑制すると PHP が遅くなるという噂について : a++ My RSS 管理人ブログ」で、@がある場合とない場合で速度比較をして以下のような結果が出た。

・・・遅い・・・遅すぎる・・・ここまでパフォーマンス悪くなるとは・・・

細かい処理とはいえ、最大10倍違うわけですから・・・


すいません、完全なる私の敗北です。これからはちゃんと isset() とか死ぬほど使います (_o_)

これに各所で反応があった。

私もいくつか書きたいことがあるので、書いておくことにする。


速度比較
まず、はてブコメントで一人突っ込んでる以外、誰も突っ込んでいないので一つ突っ込んでおく。 「「@」でエラー抑制すると PHP が遅くなるという噂について : a++ My RSS 管理人ブログ」で
if (@$a["hoge"]) $b = $a["hoge"];
if (isset($a["hoge"])) $b = $a["hoge"];
を比較しているが、それはおかしいんじゃないですかね? $a["hoge"]の値が0や""などのFalseとなる値の場合、2つの結果は異なる。 比較に使ったコードによっては"$b = $a["hoge"];"の実行回数が異なり、結果に影響が出てくる可能性もある。
正しくは
if (isset($a["hoge"]) && $a["hoge"]) $b = $a["hoge"];
と比較すべきだろう。

まぁ、書いてる人はわかってるのかもしれないし、わかりきってるから誰も突っ込まないのかもしれないが。

$a["hoge"] = 1の場合と何もセットされていない場合でそれぞれベンチマークを取ってみると
<?php
require_once 'Benchmark/Timer.php';
define('COUNT', 1000000);	//100万回

set_time_limit(0);
error_reporting(0);

$t = new Benchmark_Timer;
$a["hoge"] = 1;	//←コレをコメントアウトした場合としてない場合で比較
$t->start();

//ループのみ
for ($i = 0; $i < COUNT; ++$i) {
	//何も処理しない
}
$t->setMarker('ループのみ');

//@$a["hoge"]
for ($i = 0; $i < COUNT; ++$i) {
	if(@$a["hoge"]){
		$b = $a["hoge"];
	}
}
$t->setMarker('@$a["hoge"]');

//$a["hoge"]
for ($i = 0; $i < COUNT; ++$i) {
	if($a["hoge"]){
		$b = $a["hoge"];
	}
}
$t->setMarker('$a["hoge"]');

//isset($a["hoge"])
for ($i = 0; $i < COUNT; ++$i) {
	if(isset($a["hoge"])){
		$b = $a["hoge"];
	}
}
$t->setMarker('isset($a["hoge"])');

//isset($a["hoge"]) && $a["hoge"]
for ($i = 0; $i < COUNT; ++$i) {
	if(isset($a["hoge"]) && $a["hoge"]){
		$b = $a["hoge"];
	}
}
$t->setMarker('isset($a["hoge"]) && $a["hoge"]');

$t->stop();
$t->display();
?>

・$a["hoge"] = 1をコメントアウトしない場合

 time indexex time%
Start1210427266.79859000-0.00%
ループのみ1210427267.675794000.8772045.96%
@$a["hoge"]1210427271.301267003.62547324.62%
$a["hoge"]1210427274.535024003.23375721.96%
isset($a["hoge"])1210427277.465340002.93031619.90%
isset($a["hoge"]) && $a["hoge"]1210427281.522568004.05722827.55%
Stop1210427281.523958000.0013900.01%
total-14.725368100.00%

@$a["hoge"]は$a["hoge"]に比べてエラー制御が入った分遅く、isset($a["hoge"])はそれらよりも速い。 isset($a["hoge"]) && $a["hoge"]は当然、両方チェックしているために遅い。

・$a["hoge"] = 1をコメントアウトした場合

 time indexex time%
Start1210427332.12280100-0.00%
ループのみ1210427333.191303001.0685025.57%
@$a["hoge"]1210427341.067418007.87611541.07%
$a["hoge"]1210427348.237804007.17038637.39%
isset($a["hoge"])1210427349.698059001.4602557.62%
isset($a["hoge"]) && $a["hoge"]1210427351.298511001.6004528.35%
Stop1210427351.298655000.0001440.00%
total-19.175854100.00%

$a["hoge"]に値がセットされていないとisset($a["hoge"])は明らかに速い。 isset($a["hoge"]) && $a["hoge"]ではisset($a["hoge"])がTrueなので$a["hoge"]は評価されず速い。 つまり、isset($a["hoge"])は速いが、isset($a["hoge"]) && $a["hoge"]が速いかどうかはデータによる。

@が遅いから何?

@を付けることで確かに遅くなる。 issetで判定するだけで十分であれば、そちらに書き直すことで10倍近い差が出ることもありうる。 しかし、これは100万回実行した結果である。

大切なのは数秒のスピードアップ?それとも? - Web屋のネタ帳」では
100万回とか1000万回ループするfor文なんてものを書くのは実際の開発現場においてよくあることなんだろうか?
とある。

確かに普通はそんなコードを書くことはないだろう。 しかし、はてブコメントで突っ込まれているように、100回の@を使うスクリプトが10000回リクエストされることはある考えられるし、1万回ループするコードを書かなければならない場合もないとは言い切れない。 10倍もの速度差があると0.1秒かかる処理は1秒になる。 Webページでは1秒もかかれば重いページとされるだろう。

こう書くと非常に重要なことのように思える。 しかし、実際は@だけでそんなにも差が出ることはまずない。 誰が1/100万秒の差を感じられるというのだ。

そんなことよりも、もっと他に書き換えるべきところがあるんじゃないか?


PHPの配列の参照とデフォルト値とE_NOTICE-なんかばんざい」では
最適化するにしてもそんなミクロなとこより直すべき点が他にある。Webならファイルをgzipする、キャッシュを正しく使う、APC入れる、Apacheの不要モジュールを外すなどなど。
と書かれている。

@が遅いとか気にするよりももっと効率のいい修正はいくらでもある。 もちろん@が遅いことを気にしてissetを使うようにすることは悪いことではない。 しかし、それは可読性や保守性を犠牲にしてまで気にするようなことではない。 例えば、@を使うときというと以下のようにユーザからの入力を取得する場合があるだろう。

<?php
$title = @$_REQUEST["title"];	//"title"が存在しないと発生するE_NOTICEを抑制
$name = @$_REQUEST["name"];
$comment = @$_REQUEST["comment"];
?>
これをissetを使って書くと
<?php
if(isset($_REQUEST["title"]))
	$title = $_REQUEST["title"];
if(isset($_REQUEST["name"]))
	$name = $_REQUEST["name"];
if(isset($_REQUEST["comment"]))
	$comment = $_REQUEST["comment"];
?>
とか
<?php
$title = (isset($_REQUEST["title"]))? $_REQUEST["title"]: null;
$name = (isset($_REQUEST["name"]))? $_REQUEST["name"]: null;
$comment = (isset($_REQUEST["comnent"]))? $_REQUEST["comment"]: null;
?>
という感じになるだろう。

まぁ、どれが読みやすいかとなると人によって意見がわかれると思う。 @演算子を知らない人が見れば1つ目は明らかに読みにくいだろうし、参考演算子が嫌いな人は3つ目が読みにくいだろう。

さて、ここで上のコードにバグがあったことに気づいた人はいるだろうか? 意図的に仕込んだ簡単なタイプミスだが
$comment = (isset($_REQUEST["comnent"]))? $_REQUEST["comment"]: null;
2つ目の"comment"が"comnent"になっている。

1つ目の@を使った書き方に比べ、issetを使った書き方では配列のキーを一回多くタイプしなければならないのだ。 タイプミスが起こりやすいだけでなく、キーを変更したときに修正しなければならない箇所が増える。 こういった場合には@演算子を使った方が良いと私は考える。

そもそもエラーを出さないというのが良くない?

はてブコメントを見ていると「エラーを出さないこと自体が良くない」という意見も見られる。

上記のユーザからの入力を取得する例のように、値があるかどうかわからないため、@やissetを使わないと
Notice: Undefined index: title in /home/user/public_html/test.php on line 2
といったE_NOTICEのエラーが発生することがある。

デフォルトではE_NOTICEは表示されないため通常は表示されないが、これはタイプミスやコード修正により、あるはずキーが存在しなかった場合に知らせてくれることもあるので、私はテスト環境では表示するようにしている。

逆に、E_NOTICEなんて表示しない方がいいという意見もあるが、これは人によると思う。 私はちょっとしたバグ発見に役に立ったことが何度かあるので表示するようにしている。 もしネットで配布するようなアプリであれば、実行環境がどんな設定かわからないのだから、E_NOTICEは出ないようにすべきだ。

まとめ

@は確かに遅くなるためissetを使った方が良い場合もある。 しかし、それは可読性や保守性を犠牲にする価値があるほど大きな差になることはほぼない。 なんでもかんでも@を使えばいいというものでもないし、逆にissetばかり使えばいいというものでもない。 場合によって適切なものを判断して使うのが良い。

PHP初心者であればこんなことを気にするよりも、綺麗なコードを書くように心掛けるべきだ。