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

gravatar Nikita Popov  — July 16, 2011 11:29   #1
For those not following the internals mailing list:

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
gravatar Mathias Verraes  — July 16, 2011 15:38   #2
In practice, most cases where you'd use the ternary operator, you'd just be comparing simple, short scalars.

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.
gravatar Steve Clay  — July 16, 2011 15:39   #3
@Nikita: So upgrading PHP really is the best micro-optimization :)
gravatar Pablo  — July 16, 2011 17:27   #4
Fabien, does the ternary operator in Twig use the ternary operator of PHP?
gravatar Fabien POTENCIER  — July 16, 2011 18:16   #5
@Pablo: Twig does not rely on the ternary operator anymore as of 1.1.1 (yet to be released).
gravatar Renaud  — July 16, 2011 22:52   #6
You could use http://fr.php.net/range to fill-in the test value or http://fr.php.net/array_fill
gravatar towamamounC  — July 18, 2011 17:47   #7
Someone essentially help to make severely posts I might state. This is the first time I frequented your website page and thus far? I surprised with the analysis you made to make this actual post incredible. Great task!
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.
gravatar dhanesh mane  — July 19, 2011 07:17   #8
hey thankx for great work and sharing it with us. benchmarking done on ternary operator is cool..
gravatar Pavel C.  — July 19, 2011 10:28   #9
everything is really faster without ternary operator... this is really unexpected behavior of PHP :(

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...
gravatar Evan Kaufman  — July 20, 2011 02:09   #10
@Mathias:
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.
gravatar Jim  — July 21, 2011 21:22   #11
Before you go changing up your code willy-nilly, run tests in your environment using scenarios that replicate your normal usage.

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?
gravatar chaleb  — August 02, 2011 23:19   #12
Oh, guys, things makes worse. It seems that cast operator has same "copy always" behavior:

see bug https://bugs.php.net/bug.php?id=50894
and vote for
gravatar Molly  — August 12, 2011 08:50   #13
HI... from Mongolia
gravatar Dominik  — August 16, 2011 03:08   #14
Hi Fabien, concider this example :

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.
gravatar Dominik  — August 16, 2011 03:15   #15
Ternary operator always returns reference to object - NOT a copy ! Thats for my previous example :

$o1 = $object->f1();
$o2 = $object->f2();

var_dump($o1===$o2);
gravatar koubel  — August 18, 2011 21:43   #16
[15] - Dominik
Yes, it returns reference for objects, but always copy for non-reference types - (int, string, array, ...)