66

What kind of performance implications are there to consider when using try-catch statements in php 5?

I've read some old and seemingly conflicting information on this subject on the web before. A lot of the framework I currently have to work with was created on php 4 and lacks many of the niceties of php 5. So, I don't have much experience myself in using try-catchs with php.

Travis
  • 848
  • 1
  • 7
  • 10

9 Answers9

74

One thing to consider is that the cost of a try block where no exception is thrown is a different question from the cost of actually throwing and catching an exception.

If exceptions are only thrown in failure cases, you almost certainly don't care about performance, since you won't fail very many times per execution of your program. If you're failing in a tight loop (a.k.a: banging your head against a brick wall), your application likely has worse problems than being slow. So don't worry about the cost of throwing an exception unless you're somehow forced to use them for regular control flow.

Someone posted an answer talking about profiling code which throws an exception. I've never tested it myself, but I confidently predict that this will show a much bigger performance hit than just going in and out of a try block without throwing anything.

Another thing to consider is that where you nest calls a lot of levels deep, it can even be faster to have a single try...catch right at the top than it is to check return values and propagate errors on every call.

In the opposite of that situation, where you find that you're wrapping every call in its own try...catch block, your code will be slower. And uglier.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • I have never seen code where they wraps each call with a `try catch` instead of using one single big `try cacth` to wrap all calls! – Marco Demaio Apr 06 '11 at 11:56
  • 3
    @Marco: it happens in code that doesn't *want* to use exceptions itself, but is forced to use an API that does. So every call into that API ends up wrapped, catching exceptions and turning them into error codes or whatever. Maybe that doesn't really arise in PHP, but it can be a hazard in other languages when different coding styles clash. The other occasion is when you want to handle the exceptions very differently according to where they come from (quietly logging some, warning the user about others, failing on others) rather than just the exception type. Then you need lots of try-catch. – Steve Jessop Apr 06 '11 at 12:24
62

I was bored and profiled the following (I left the timing code out):

function no_except($a, $b) { 
    $a += $b;
    return $a;
}
function except($a, $b) { 
    try {
        $a += $b;
    } catch (Exception $e) {}
    return $a;
}

using two different loops:

echo 'no except with no surrounding try';
for ($i = 0; $i < NUM_TESTS; ++$i) {
    no_except(5, 7);
}
echo 'no except with surrounding try';
for ($i = 0; $i < NUM_TESTS; ++$i) {
    try {
        no_except(5, 7);
    } catch (Exception $e) {}
}
echo 'except with no surrounding try';
for ($i = 0; $i < NUM_TESTS; ++$i) {
    except(5, 7);
}
echo 'except with surrounding try';
for ($i = 0; $i < NUM_TESTS; ++$i) {
    try {
        except(5, 7);
    } catch (Exception $e) {}
}

With 1000000 runs on my WinXP box run apache and PHP 5.2.6:

no except with no surrounding try = 3.3296
no except with surrounding try = 3.4246
except with no surrounding try = 3.2548
except with surrounding try = 3.2913

These results were consistent and remained in similar proportion no matter which order the tests ran.

Conclusion: Adding code to handle rare exceptions is no slower than code the ignores exceptions.

jmucchiello
  • 18,754
  • 7
  • 41
  • 61
  • the `except` function doesn't actually throw an exception. Was this your intended test? – Lea Hayes Apr 22 '11 at 22:19
  • 11
    No, the intent was to profile the code that "can" handle exceptions. Not to profile the actual throw/catch mechanism. The data demonstrates that simply putting the try/catch blocks into your code does not add significant overhead. – jmucchiello May 18 '11 at 01:17
  • Imho such test is way to simple to test for exceptions, since it is testing one liner that cant even throw an exception. Or am I unaware of some way that sum and assignment could cause and exception? – morphles May 30 '11 at 05:50
  • 1
    This should be the top answer. People should treat coding as science. – stonyau Dec 29 '17 at 09:12
  • The question asks about "performance implications" when "using try-catch statements", and u answer "Look I've found some example use cases with try-catch statements which don't impact performance", but your examples don't answer the general use case. In the general use case, an exception may actually occur, and it may occur for each iteration since it's catched and the loop can continue, which may lead to huge performance implication (creating an exception object for each iteration has a cost). – Tristan Feb 24 '22 at 16:08
27

Try-catch blocks are not a performance problem - the real performance bottleneck comes from creating exception objects.

Test code:

function shuffle_assoc($array) { 
    $keys = array_keys($array);
    shuffle($keys);
    return array_merge(array_flip($keys), $array);
}

$c_e = new Exception('n');

function no_try($a, $b) { 
    $a = new stdclass;
    return $a;
}
function no_except($a, $b) { 
    try {
        $a = new Exception('k');
    } catch (Exception $e) {
        return $a + $b;
    }
    return $a;
}
function except($a, $b) { 
    try {
        throw new Exception('k');
    } catch (Exception $e) {
        return $a + $b;
    }
    return $a;
}
function constant_except($a, $b) {
    global $c_e;
    try {
        throw $c_e;
    } catch (Exception $e) {
        return $a + $b;
    }
    return $a;
}

$tests = array(
    'no try with no surrounding try'=>function() {
        no_try(5, 7);
    },
    'no try with surrounding try'=>function() {
        try {
            no_try(5, 7);
        } catch (Exception $e) {}
    },
    'no except with no surrounding try'=>function() {
        no_except(5, 7);
    },
    'no except with surrounding try'=>function() {
        try {
            no_except(5, 7);
        } catch (Exception $e) {}
    },
    'except with no surrounding try'=>function() {
        except(5, 7);
    },
    'except with surrounding try'=>function() {
        try {
            except(5, 7);
        } catch (Exception $e) {}
    },
    'constant except with no surrounding try'=>function() {
        constant_except(5, 7);
    },
    'constant except with surrounding try'=>function() {
        try {
            constant_except(5, 7);
        } catch (Exception $e) {}
    },
);
$tests = shuffle_assoc($tests);

foreach($tests as $k=>$f) {
    echo $k;
    $start = microtime(true);
    for ($i = 0; $i < 1000000; ++$i) {
        $f();
    }
    echo ' = '.number_format((microtime(true) - $start), 4)."<br>\n";
}

Results:

no try with no surrounding try = 0.5130
no try with surrounding try = 0.5665
no except with no surrounding try = 3.6469
no except with surrounding try = 3.6979
except with no surrounding try = 3.8729
except with surrounding try = 3.8978
constant except with no surrounding try = 0.5741
constant except with surrounding try = 0.6234
Brilliand
  • 13,404
  • 6
  • 46
  • 58
10

Generally, use an exception to guard against unexpected failures, and use error checking in your code against failures that are part of normal program state. To illustrate:

  1. Record not found in database - valid state, you should be checking the query results and messaging the user appropriately.

  2. SQL error when trying to fetch record - unexpected failure, the record may or may not be there, but you have a program error - this is good place for an exception - log error in error log, email the administrator the stack trace, and display a polite error message to the user advising him that something went wrong and you're working on it.

Exceptions are expensive, but unless you handle your whole program flow using them, any performance difference should not be human-noticeable.

Aeon
  • 6,467
  • 5
  • 29
  • 31
6

I have not found anything on Try/Catch performance on Google but a simple test with a loop throwing error instead of a IF statement produce 329ms vs 6ms in a loop of 5000.

Patrick Desjardins
  • 136,852
  • 88
  • 292
  • 341
  • 2
    I too would like to see the code that you used for this if you still have it :-) – Lea Hayes Apr 22 '11 at 22:21
  • @Patrick Desjardins: could you post the code for you test or give us a link to it. – Marco Demaio Dec 29 '12 at 17:59
  • 3
    Loop throwing exception costs more, as it actually throws an exception. But if you have only try-catch block, and no exception is thrown, then code is not any slower as @jmucchiello tested. So you didn't test try-catch performance, but handling exceptions performance, which of course is much slower. It's of course useful to know it, because after your test I know that it's pointless to use exceptions to control flow of your application. Exceptions are good only when they are rare, and helps you handle error states of the application. See Steve Jessop's answer as well. – Karol May 15 '13 at 00:58
3

Sorry to post to a very old message, but I read the comments and I somewhat disagree, the difference might be minimal with simple piece of codes, or it could be neglectable where the Try/Catch are used for specific parts of code that are not always predictable, but I also believe (not tested) that a simple:

if(isset($var) && is_array($var)){
    foreach($var as $k=>$v){
         $var[$k] = $v+1;
    }
}

is faster than

try{
    foreach($var as $k=>$v){
        $var[$k] = $v+1;
    }
}catch(Exception($e)){
}

I also believe (not tested) that a:

<?php
//beginning code
try{
    //some more code
    foreach($var as $k=>$v){
        $var[$k] = $v+1;
    }
    //more code
}catch(Exception($e)){
}
//output everything
?>

is more expensive than have extra IFs in the code

Fabrizio
  • 3,734
  • 2
  • 29
  • 32
  • 3
    The question was if the "more expensive" method (try/catch) can degrade performance, or if the impact is minimal. – StefanNch Sep 30 '15 at 10:46
2

I updated Brilliand's test code to make it's report more understandable and also statitistically truthfully by adding more randomness. Since I changed some of its tests to make them more fair the results will be different, therefore I write it as different answer.

My tests executed by: PHP 7.4.4 (cli) (built: Mar 20 2020 13:47:45) ( NTS )

<?php

function shuffle_assoc($array) {
    $keys = array_keys($array);
    shuffle($keys);
    return array_merge(array_flip($keys), $array);
}

$c_e = new Exception('n');

function do_nothing($a, $b) {
    return $a + $b;
}
function new_exception_but_not_throw($a, $b) {
    try {
        new Exception('k');
    } catch (Exception $e) {
        return $a + $b;
    }
    return $a + $b;
}
function new_exception_and_throw($a, $b) {
    try {
        throw new Exception('k');
    } catch (Exception $e) {
        return $a + $b;
    }
    return $a + $b;
}
function constant_exception_and_throw($a, $b) {
    global $c_e;
    try {
        throw $c_e;
    } catch (Exception $e) {
        return $a + $b;
    }
    return $a + $b;
}

$tests = array(
    'do_nothing with no surrounding try'=>function() {
        do_nothing(5, 7);
    },
    'do_nothing with surrounding try'=>function() {
        try {
            do_nothing(5, 7);
        } catch (Exception $e) {}
    },
    'new_exception_but_not_throw with no surrounding try'=>function() {
        new_exception_but_not_throw(5, 7);
    },
    'new_exception_but_not_throw with surrounding try'=>function() {
        try {
            new_exception_but_not_throw(5, 7);
        } catch (Exception $e) {}
    },
    'new_exception_and_throw with no surrounding try'=>function() {
        new_exception_and_throw(5, 7);
    },
    'new_exception_and_throw with surrounding try'=>function() {
        try {
            new_exception_and_throw(5, 7);
        } catch (Exception $e) {}
    },
    'constant_exception_and_throw with no surrounding try'=>function() {
        constant_exception_and_throw(5, 7);
    },
    'constant_exception_and_throw with surrounding try'=>function() {
        try {
            constant_exception_and_throw(5, 7);
        } catch (Exception $e) {}
    },
);
$results = array_fill_keys(array_keys($tests), 0);
$testCount = 30;
const LINE_SEPARATOR = PHP_EOL; //"<br>";

for ($x = 0; $x < $testCount; ++$x) {
    if (($testCount-$x) % 5 === 0) {
        echo "$x test cycles done so far".LINE_SEPARATOR;
    }
    $tests = shuffle_assoc($tests);
    foreach ($tests as $k => $f) {
        $start = microtime(true);
        for ($i = 0; $i < 1000000; ++$i) {
            $f();
        }
        $results[$k] += microtime(true) - $start;
    }
}
echo LINE_SEPARATOR;
foreach ($results as $type => $result) {
    echo $type.' = '.number_format($result/$testCount, 4).LINE_SEPARATOR;
}

The results are following:

do_nothing with no surrounding try = 0.1873
do_nothing with surrounding try = 0.1990
new_exception_but_not_throw with no surrounding try = 1.1046
new_exception_but_not_throw with surrounding try = 1.1079
new_exception_and_throw with no surrounding try = 1.2114
new_exception_and_throw with surrounding try = 1.2208
constant_exception_and_throw with no surrounding try = 0.3214
constant_exception_and_throw with surrounding try = 0.3312

Conclusions are:

  • adding extra try-catch adds ~0.01 microsecond per 1000000 iterations
  • exception throwing and catching adds ~0.12 microsecond (x12 compared to previous when nothing were thrown and nothing were catched)
  • exception creation adds ~0.91 microsecond (x7.6 compared to execution of try-catch mechanism calculated in previous line)

So the most expensive part is exception creation - not the throw-catch mechanism, however latter made this simple routine 2 times slower compared to do_nothing scenarion.

* all measurements in conclusions are rounded and are not pretends to be scientifically accurate.

Arkemlar
  • 380
  • 4
  • 12
  • So If you "fight for every microsecond" avoiding exceptions _probably_ makes sense for you, otherwise - not. – Arkemlar Apr 09 '20 at 21:42
1

Thats a very good question!

I have tested it many times and never saw any performance issue ;-) It was true 10 years ago in C++ but I think today they have improved it a lot since its so usefull and cleaner.

But I am still afraid to surround my first entry point with it:

try {Controller::run();}catch(...)

I didnt test with plenty of functions call and big include .... Is anyone has fully test it already?

Tom
  • 11
  • 1
-9

Generally speaking, they're expensive and not worthwhile in PHP.

Since it is a checked expressions language, you MUST catch anything that throws an exception.

When dealing with legacy code that doesn't throw, and new code that does, it only leads to confusion.

Good luck!

Ian P
  • 12,840
  • 6
  • 48
  • 70