Correct use of PHP’s ‘at’ operator with speed benchmark

In PHP placing an @ symbol in front of an expression (variable or function call) tells php to suppress any error messages that expression generates. I find this to be a handy piece of syntactic sugar. When used correctly the gains in code readability far outweigh the costs in terms of performance (which I benchmark below). Some people argue that suppressing errors is a mistake and can mask problems so therefore this technique should never be used. I agree with the idea that suppressing errors is bad. At the same time if I don’t care if something in a 4 level nested array is null, then suppressing PHP’s chatter is doing me a huge favor.

Let’s look at an example of where the @-operator shines. Consider trying to get a value out of a nested array, which may or may not be set such as $response[‘STATUS’][‘ERRORS’][‘ERROR_COUNT’], which is a typical thing to see in SOAP based XML responses from enterprisey APIs.

One approach might be:

if(isset($response) &&
   isset($response['STATUS']) && 
   isset($response['STATUS']['ERRORS']) && 
   isset($response['STATUS']['ERRORS']['ERROR_COUNT'])) {
	$error_count = $response['STATUS']['ERRORS']['ERROR_COUNT'];
}

Although isset() doesn’t have a problem with this shorter version either. Thank you to my friend for pointing this out!

if(isset($response['STATUS']['ERRORS']['ERROR_COUNT'])) {
	$error_count = $response['STATUS']['ERRORS']['ERROR_COUNT'];
}

With the @-operator:

$error_count = @$response['STATUS']['ERRORS']['ERROR_COUNT'];

I like the last method because it is cleanest. I don’t care if $error_count is zero or null. The @-operator, being a somewhat lazy technique pairs well with another of PHP’s lazy at best but deeply flawed at worst ‘features’ in that NULL, “0”, 0, array(), and false are ‘falsey’ and can be used interchangeably when doing comparisons with plain ‘==’. By using three equal signs ‘===’ the types of the variables are also considered and that is generally the preferred method of comparing things, but that level of precision isn’t always required.

Notes about the @ sign in PHP:

  • If you delcared a custom error handler with set_error_handler() that will still get called.
  • It only works on expressions (things that give back a value). So it does not work on if/then statements, loops, and class structures, etc. This was a wise choice by the PHP community.
  • The fact that it only works on expressions greatly reduces the unanticipated side effects that can result. In this sense it is nothing like ON ERROR RESUME NEXT, an infamous language feature in Visual Basic and Classic ASP, which chugs past errors. The previous error can still be checked for in a sort of poor man’s try/catch block. ON ERROR RESUME NEXT sucks and makes me want to hurl just thinking about it.

Some people really hate the @-operator:

Most of the arguments against the @-operator come down to misuse and then over reaction. The fact is inexperienced and inept programmers can take any language feature and come back with a hairball of unmaintainable code.

As I demonstrated above, the @-operator is great when digging through arrays such as complex DOM objects. This is especially true with optional keys. It should not be used when calling external resources like the file system, database, APIs, etc. In those situations, try/catch blocks should be used to make sure if something goes wrong it gets logged and cleaned up properly. The @-operator is not a substitute for a try/catch!

The second major knock against the @-operator is the alleged performance penalty. Let’s do some benchmarking:

laurence@blog $ php -v
PHP 5.3.24 (cli) (built: Apr 10 2013 18:38:43)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies

laurence@blog $ cat php-at-operator-test.php
<?php
error_reporting(E_ALL ^ E_NOTICE);

$OPERATIONS = 100000;

// test using @-operator
$time_start = microtime(true);
for($i=0; $i<$OPERATIONS; $i++) {
  $error_count = @$response['STATUS']['ERRORS']['ERROR_COUNT'];
}
$duration = (microtime(true) - $time_start);

echo "With the @-operator:" . PHP_EOL;
echo "\tTotal time:\t\t" . $duration . PHP_EOL;
echo "\tTime per operation:\t" . number_format($duration / $OPERATIONS, 10) . PHP_EOL;
echo PHP_EOL;


// test using isset()
$time_start = microtime(true);
for($i=0; $i<$OPERATIONS; $i++) {
        if(isset($response['STATUS']['ERRORS']['ERROR_COUNT'])) {
             $error_count = $response['STATUS']['ERRORS']['ERROR_COUNT'];
        }
}
$duration = (microtime(true) - $time_start);

echo "Using isset():" . PHP_EOL;
echo "\tTotal time:\t\t" . $duration . PHP_EOL;
echo "\tTime per operation:\t" . number_format($duration / $OPERATIONS, 10) . PHP_EOL;
echo PHP_EOL;
laurence@blog $ php php-at-operator-test.php
With the @-operator:
        Total time:             0.19701099395752
        Time per operation:     0.0000019701

Using isset():
        Total time:             0.015001058578491
        Time per operation:     0.0000001500

For my limited testing with PHP 5.3.24 on a 6 core box looks like the @-operator is ~13 times slower than using isset(). That sounds like a lot, but let's look at the penalty per use, which is 0.0000018201 seconds, or ~1.82 microseconds. An application could do approximately 550 @-operator uses, and it would impact the response time by just 1 millisecond. If a single page request does 550 @-operator look-ups and every millisecond counts then you have a problem. Probably what matters more is overall memory consumption, transactionality, caching, code cleanliness, ease of maintainability, logging, unit tests, having customers, etc... Still it is good to have a solid measure when arguing the case either way. In the future as CPUs get faster and cheaper, I expect the performance penalty to shrink.

This entry was posted in Code and tagged , . Bookmark the permalink.

3 Responses to Correct use of PHP’s ‘at’ operator with speed benchmark

  1. Igor Santos says:

    I’ve run this test in my office’s Windows box running on a poor Core 2 Quad and my cheap Digital Ocean empty Ubuntu server, but both running PHP 5.5. First, I would say that you by using 5.3 is quite outdated; benchmarking in such an environment is somewhat contraproductive since the current PHP versions are usually faster and what (we expect) people would be using.

    That said, here are my two cents:
    1. I got a much shorter difference on both machines, between 7.5 in my VPS and 4.5 in the Desktop. That seems to happen because isset got slower (?).
    2. I think that testing against isset($var)? $var : null makes more sence since actually the two tests are doing different things; in the first you WILL have a var, even though it’s value is null; in the second the var may not be defined – and by using the ternary you have the exact same result as the first test, and reduce just a little bit the verbosity as well (a common if for the same result would be much much more verbose); On the other hand, using the ternary made the second test a little bit slower, bringing differences to 4.5 VPS and 3.5 Desktop.

    • Igor Santos says:

      That said, I still have one more thing to add: I found the idea great and it helped me to alleviate the prejudice I had against the @! I’ll be using that in my code now since I always hated isset($var)? $var : ''. I’m eagerly waiting for the isset ternary operator (??) but meanwhile… 🙂

  2. Laurence says:

    Good points Igor! Indeed PHP 5.3 is old… You are correct that it matters down to the exact version of PHP, since the internals change potentially in each release. Declaring $error_count in the second test would make it more apples to apples. If I get around to this again I’ll update it.