Performance Impact of PHP Exceptions
Exceptions in programming languages provide a way for the application code to raise and handle exceptional situations, and PHP language is not an exception.
PHP provides various capabilities to use Exceptions in PHP code, such as global exception handlers, basic try
/catch
syntax, cascaded and combined catch
syntax, catching exceptions just with its type, and the finally
blocks.
In PHP 8.0, several of PHP internal functions will throw standard TypeError
and ValueError
exceptions, which means you will be seeing more and more exceptions and exception handling in PHP code.
This article tries to discover the performance impact of handling and throwing exceptions.
Test cases
Note that Exceptions with throw
keyword, or the use of try
/catch
blocks has a very small execution time. It is highly unlikely that use of Exceptions is the bottleneck of any application.
This benchmark is meant to compare the relative performance impact of using Exceptions at all, and how it progressed over PHP versions.
In the benchmark, we will take a look at 6 test cases:
- No
try
/catch
: Test without any exception handling at all. -
try
/catch
,return
within try block: Return a value from inside atry
block. -
try
/catch
, no exceptions: Use atry
/catch
block, but do notthrow
any Exception. -
try
/multiplecatch
, no exceptions: Similar to #3, but with multiple cascadingcatch
blocks. -
try
/catch
,throw
exception: Catch the Exception from firstcatch
block. -
try
/multiplecatch
,throw
exception: Catch the Exception from lastcatch
block.
1. No try
/catch
This is the base test that there are no any try
/catch
blocks at all.
function case_return(): ?int {
return null;
}
2. try
/catch
, return
within try
A simple try
/catch
block, but returns immediately within the try
block. This is the first test case with a try
/catch
block, and should indicate the performance impact of using it, but not catching any exception, and should reflect the happy-path of an application.
function case_try_successful_return(): ?int {
try {
return null;
}
catch(Exception $ex) {}
}
3. try
/catch
, no exceptions
A try
/catch
block, but it does not return
nor throw
any exceptions. Another test case with try
/catch
blocks, but the code proceeds beyond it because no exceptions are thrown.
function case_try_successful_single_exception(): void {
try {}
catch(Exception $ex) {}
}
4. try
/multiple catch
, no exceptions
Similar to test case #3, but uses multiple cascading catch
blocks.
function case_try_successful_multi_exception(): void {
try {}
catch(InvalidArgumentException $ex) {}
catch(LogicException $ex) {}
catch(Exception $ex) {}
}
5. try
/catch
, throw
exception
This is the first test that throw
s an exception. This will indicate the performance impact of creating the Exception object in contrast to case #3.
function case_try_throw_single_exception(): void {
try {
throw new InvalidArgumentException();
}
catch(InvalidArgumentException $ex) {}
catch(LogicException $ex) {}
catch(Exception $ex) {}
}
6. try
/multiple catch
, throw
exception
A try
/catch
block, but the exception is caught at the last catch
clause. This test case is to see if there is any noticeable performance impact when PHP evaluates multiple catch
blocks.
function case_try_throw_multi_exception(): void {
try {
throw new Exception();
}
catch(InvalidArgumentException $ex) {}
catch(LogicException $ex) {}
catch(Exception $ex) {}
}
Test subjects
The following PHP versions were tested:
- 8.0 beta 3
- 7.4.10
- 7.3.22
- 7.2.33
Test results
7.2 | 7.3 | 7.4 | 8.0 | |
---|---|---|---|---|
1. No try /catch |
0.0116 | 0.0085 | 0.0070 | 0.0060 |
2. try /catch , return within try{} |
0.0159 | 0.0120 | 0.0100 | 0.0080 |
3. try /catch , no exceptions |
0.0192 | 0.0161 | 0.0124 | 0.0085 |
4. try/multiple catch, no exceptions | 0.0201 | 0.0150 | 0.0125 | 0.0081 |
5. try /catch , throw exception |
0.1048 | 0.0732 | 0.0544 | 0.0540 |
6. try /catch , multiple catch blocks |
0.1102 | 0.0680 | 0.0561 | 0.0550 |
Above are execution times average from 5 runs, with each case executed 100K times. Rounded to 4 decimals, and raw values are available here with up to 12 decimals.
Conclusion
Throughout all PHP versions tested, the try
/catch
blocks themselves have relatively small performance impact. However, throwing an Exception, which requires PHP to collect the stack trace and create an Exception object can be costly, and is up to 9 times slow.
Note that in absolute numbers, throwing an exception one million times took 0.5 seconds, which indicates that the absolute performance cost to throw an exception is quite low, and will not be a bottleneck in any application.
Exceptions are meant to be for... exceptional situations. Using them to replace a semantic function (such as a Book::exists($book_id)
) might be semantically wrong, because a negative value (such as null
) is still an expected result.
However, using Exceptions as a way to communicate exceptional situations that the code in context cannot handle, will be a perfect candidate to use an Exception.
In PHP 8.0, several internal functions start to throw Exceptions, including the new ValueError
exception. So you use Exceptions where necessary, because although the absolute cost of throwing an exception is higher, it hardly makes a difference in the big scheme of things.
The benefits of properly using Exceptions, such as a global exception handler, the ability to further log and process said exceptions, and more cleaner architecture in unhappy-path handling far outweighs the performance cost of Exceptions.