TL;DR
Is it faster to do a file_exists, or checking the return value from @include (i.e. suppressing errors)?
In this context, you may assume I’m using absolute paths and not relying on include_path resolving.
Elaborate version
With all the conditional code that frameworks and/or software has in their bootstrap process, I started wondering about the fastest way to fail, without sacrificing gracefulness. I have a specific use case in which this thought entered my mind, namely including a config file which doesn’t necessarily exist.
Both operations do a stat anyway, so there’s no way to avoid disk access. The latter option has the added benefit of having read and parsed the file when it succeeds, so in my mind that’s already quite a big bonus.
This is really an extreme micro-optimization, especially in my case where it’s only about one file, but the question still bugs me to death and I don’t know how to test this properly myself.
Brute force testing after accept
After I got a proper answer, I did some brute force testing. I didn’t want to do this before, since I wanted informed answers from the field. Also, I didn’t want to base this solely on my brute force testing, because I’m not at all sure this is the proper way to test this. (Actually, I’m quite certain it isn’t.)
What I did was run the following code snippets:
<?php
$rounds = 1e6;
$start = microtime(true);
while ($rounds--) {
@include "i-dont-exist";
clearstatcache();
}
echo microtime(true) - $start, PHP_EOL;
…and…
<?php
$rounds = 1e6;
$start = microtime(true);
while ($rounds--) {
file_exists("i-dont-exist");
clearstatcache();
}
echo microtime(true) - $start, PHP_EOL;
…and…
<?php
$rounds = 1e6;
$start = microtime(true);
while ($rounds--) {
@include "i-exist";
clearstatcache();
}
echo microtime(true) - $start, PHP_EOL;
…and…
<?php
$rounds = 1e6;
$start = microtime(true);
while ($rounds--) {
if (file_exists("i-exist")) @include "i-exist";
clearstatcache();
}
echo microtime(true) - $start, PHP_EOL;
The results were:
Non-existing file
@include: 27.090675830841
file_exists: 1.0596489906311
Existing file
@include: 19.758506059647
file_exists + include: 22.083800077438
What can we conclude from this? Well, at least, as per the links provided in the answer by @goldencrater, suppressing errors is expensive.
In general, I believe suppressing errors is also stupid – unless it’s an informed choice, a criteria which I think my use case validates.
If you’re going to fail after including a including a file fails, in my mind suppressing the error from include is justifiable, since the cost of failing is only inferred on a failing request. (I.e. the error isn’t suppressed and ignored.) Putting a heavy cost on a failing request is much less worse than putting a small cost on every request.
Even though error supression (@) has a huge overhead in itself, eliminating the file_exists call is always faster.
The guys behind the Smarty templating engine changed their entire codebase to use @ instead of file_exists (and similar) checks and ended up with a major speed increase. Here’s a blog post from one of the authors detailing the change: http://blog.rodneyrehm.de/archives/12-Improving-Disk-IO-in-PHP-Apps.html .