Performance of try-catch in php

PhpPerformanceException HandlingTry Catch

Php Problem Overview


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.

Php Solutions


Solution 1 - Php

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.

Solution 2 - Php

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.

Solution 3 - Php

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

Solution 4 - Php

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.

Solution 5 - Php

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.

Solution 6 - Php

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

Solution 7 - Php

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?

Solution 8 - Php

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.

Solution 9 - Php

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!

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTravisView Question on Stackoverflow
Solution 1 - PhpSteve JessopView Answer on Stackoverflow
Solution 2 - PhpjmucchielloView Answer on Stackoverflow
Solution 3 - PhpBrilliandView Answer on Stackoverflow
Solution 4 - PhpAeonView Answer on Stackoverflow
Solution 5 - PhpPatrick DesjardinsView Answer on Stackoverflow
Solution 6 - PhpFabrizioView Answer on Stackoverflow
Solution 7 - PhpTomView Answer on Stackoverflow
Solution 8 - PhpArkemlarView Answer on Stackoverflow
Solution 9 - PhpIan PView Answer on Stackoverflow