The PHP Ternary Operator: Fast or not?
Fabien Potencier
July 16, 2011
People like micro-optimizations. They are easy to understand, easy to apply... and useless. But some time ago, while reviewing pull requests for Twig, I read an interesting discussion about the performance of the ternary operator in PHP (thanks to @nikic for the investigation).
Do you know which following snippet is the fastest (of course, they do exactly the same)?
// snippet 1 $tmp = isset($context['test']) ? $context['test'] : ''; // snippet 2 if (isset($context['test'])) { $tmp = $context['test']; } else { $tmp = ''; }
The right answer is: it depends. Most of the time, they are about the same
speed and you don't need to care. But if $context['test'] contains a large
amount of data, snippet 2 is much faster than snippet 1.
Here is the code I have used to test different scenarii:
$context = array('test' => true); // optionally fill-in the test value with lots of data for ($i = 0; $i < 100000; $i++) { $context['test'][$i] = $i; } // you can also just create a big string // $context = str_repeat(' ', 1000000); // benchmark $time = microtime(true); for ($i = 0; $i < 100; $i++) { // the snippet of code to benchmark $tmp = isset($context['test']) ? $context['test'] : ''; } printf("TIME: %0.2d\n", (microtime(true) - $time) * 1000);
Note that the absolute performance numbers are meaningless here. We just want to compare the speed between different snippets.
On my laptop, snippet 1 takes more than two seconds, whereas snippet 2 takes about 0.05ms. That's a big difference! But if the variable to test does not host many data, the speed is almost the same.
So, why does the ternary operator become so slow under some circumstances? Why does it depend on the value stored in the tested variable?
The answer is really simple: the ternary operator always copies the value
whereas the if statement does not. Why? Because PHP uses a technique known
as copy-on-write: When assigning a value to a variable, PHP does not
actually create a copy of the content of the variable until it is modified.
When you write a statement like $tmp = $context['test'], very little
happens: the $tmp variable just becomes a reference to the
$context['test'] one; that's why it's really fast. But as soon you want to
modify the variable, PHP needs to copy the original one:
$tmp = $context['test']; // the copy happens now $tmp[] = true; // copy also happens if the original variable changes // $context['test'][] = true;
To sum up, the speed of the ternary operator is directly related to the time it takes to copy the result of the statement, even if it is not strictly needed. And copying an array of 100000 elements takes time.
If you are using PHP 5.3, there is a simpler way to express our statement with
the new ?: construct:
$tmp = $context['test'] ?: '';
Unfortunately, this new construct has the same drawbacks as the standard one as far as performance is concerned, even if PHP should probably be able to optimize the case where the variable exists.




Discussion
Arnaud Le Blanc has already submitted a patch to resolve this issue. It changes the behavior of the ternary toward not copying the value in some cases (most cases? / all cases? I don't know exactly.).
Here is part of the conversation (I couldn't find the start):
http://www.mail-archive.com/internals@lists.php.net/msg51926.html
In any case, the decision to use ternary operators should depend on how they make the code more readable, or not, in each individual situation. As with all micro-optimizations, we should always keep in mind that developer time is more precious than processor time.
But it is nice to know how things work internally -- I had no clue about the copy-on-write :-) So thanks for the writeup.
Excellent website. Plenty of helpful information here. I¡¦m sending it to some buddies ans also sharing in delicious. And certainly, thank you on your effort!
hello!,I really like your writing very much! share we be in contact extra about your post on AOL? I need a specialist in this house to resolve my problem. Maybe that's you! Having a look ahead to peer you.
will there be some effort to optimize symfony 1 too? I already created a ticket (http://trac.symfony-project.org/ticket/9867) , but this could change a lot of the code...
Not if you use a lot of chained methods on objects, like you do in symfony or especially doctrine.
The ternary looks cleaner than an if statement, but if it implies a performance hit for the kind of tools I use, I think I'll be using more if..else's.
Keep in mind:
- Laptops, workstations, and servers will all perform differently. Where are you testing?
- Strings behave differently than arrays. What are you testing?
- Size matters. How much data are you working with?
In my tests, I actually found ?: to be the fastest when using strings under 64 characters long. It was only 1-2µs faster than if/else, but we're talking about micro-optimizations, right?
see bug https://bugs.php.net/bug.php?id=50894
and vote for
class test {
public $context;
function f1(){
return !empty($this->context['test']) ? $this : '';
}
function f2(){
if(!empty($this->context['test']))
return $this;
else return '';
}
function __construct(){
$this->context = array('test' => array());
// optionally fill-in the test value with lots of data
for ($i = 0; $i < 100000; $i++) {
$this->context['test'][$i] = $i;
}
}
}
// benchmark
$time = microtime(true);
$object = new test();
for ($i = 0; $i < 100; $i++) {
// the snippet of code to benchmark
$tmp = $object->f1();
}
printf("TIME: %0.2d\n", (microtime(true) - $time) * 1000);
Here there is no performance difference, no matter which function will you choose to return $this on large object.
$o1 = $object->f1();
$o2 = $object->f2();
var_dump($o1===$o2);
Yes, it returns reference for objects, but always copy for non-reference types - (int, string, array, ...)