From 0be5bb0b2c45d9eb9c7873f08978758b9a531758 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 27 Apr 2025 14:05:10 +0200 Subject: [PATCH] Improve performance of instantiating exceptions/errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The class structure is fixed, so it makes no sense to go through all the logic of looking up property info etc. This patch introduces a local function `zend_obj_prop_num_checked()` to help with that. For this benchmark: ```php for ($i = 0; $i < 1000000; $i++) new Error; ``` On an i7-4790: ``` Benchmark 1: ./sapi/cli/php x.php Time (mean ± σ): 141.6 ms ± 9.3 ms [User: 138.7 ms, System: 2.0 ms] Range (min … max): 135.4 ms … 177.7 ms 20 runs Benchmark 2: ../RELx64_old/sapi/cli/php x.php Time (mean ± σ): 214.1 ms ± 7.0 ms [User: 207.6 ms, System: 5.0 ms] Range (min … max): 206.6 ms … 230.9 ms 13 runs Summary ./sapi/cli/php x.php ran 1.51 ± 0.11 times faster than ../RELx64_old/sapi/cli/php x.php ``` For this benchmark: ```php for ($i = 0; $i < 1000000; $i++) new Exception("message", 0, null); ``` On an i7-4790: ``` Benchmark 1: ./sapi/cli/php x.php Time (mean ± σ): 184.3 ms ± 9.5 ms [User: 181.2 ms, System: 1.8 ms] Range (min … max): 173.8 ms … 205.1 ms 15 runs Benchmark 2: ../RELx64_old/sapi/cli/php x.php Time (mean ± σ): 253.7 ms ± 7.0 ms [User: 247.6 ms, System: 4.6 ms] Range (min … max): 245.7 ms … 263.7 ms 11 runs Summary ./sapi/cli/php x.php ran 1.38 ± 0.08 times faster than ../RELx64_old/sapi/cli/php x.php ``` For this benchmark: ```php for ($i = 0; $i < 1000000; $i++) new ErrorException("message", 0, 0, "xyz", 0, null); ``` On an i7-4790: ``` Benchmark 1: ./sapi/cli/php x.php Time (mean ± σ): 223.6 ms ± 7.7 ms [User: 220.1 ms, System: 2.4 ms] Range (min … max): 216.9 ms … 242.5 ms 12 runs Benchmark 2: ../RELx64_old/sapi/cli/php x.php Time (mean ± σ): 343.5 ms ± 8.1 ms [User: 337.1 ms, System: 4.6 ms] Range (min … max): 337.3 ms … 362.8 ms 10 runs Summary ./sapi/cli/php x.php ran 1.54 ± 0.06 times faster than ../RELx64_old/sapi/cli/php x.php ``` --- UPGRADING | 3 ++ Zend/zend_exceptions.c | 96 ++++++++++++++++++++++++------------------ 2 files changed, 57 insertions(+), 42 deletions(-) diff --git a/UPGRADING b/UPGRADING index 6fd01ab0e370f..75bd65ffe1c07 100644 --- a/UPGRADING +++ b/UPGRADING @@ -469,6 +469,9 @@ PHP 8.5 UPGRADE NOTES 14. Performance Improvements ======================================== +- Core: + . Creating exceptions objects is now up to 50% faster. + - ReflectionProperty: . Improved performance of the following methods: getValue(), getRawValue(), isInitialized(), setValue(), setRawValue(). diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index f9d0ae8ea8173..38145aca215b4 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -254,41 +254,45 @@ ZEND_API void zend_clear_exception(void) /* {{{ */ } /* }}} */ +/* Same as OBJ_PROP_NUM(), but checks the offset is correct when Zend is built in debug mode. */ +static zend_always_inline zval *zend_obj_prop_num_checked(zend_object *object, uint32_t prop_num, zend_string *str) +{ +#if ZEND_DEBUG + zend_class_entry *old_scope = EG(fake_scope); + EG(fake_scope) = i_get_exception_base(object); + const zend_property_info *prop_info = zend_get_property_info(object->ce, str, true); + ZEND_ASSERT(OBJ_PROP_TO_NUM(prop_info->offset) == prop_num); + EG(fake_scope) = old_scope; +#else + ZEND_IGNORE_VALUE(str); +#endif + return OBJ_PROP_NUM(object, prop_num); +} + static zend_object *zend_default_exception_new(zend_class_entry *class_type) /* {{{ */ { - zval tmp; - zval trace; - zend_class_entry *base_ce; zend_string *filename; zend_object *object = zend_objects_new(class_type); object_properties_init(object, class_type); + zval *trace = zend_obj_prop_num_checked(object, 5, ZSTR_KNOWN(ZEND_STR_TRACE)); if (EG(current_execute_data)) { - zend_fetch_debug_backtrace(&trace, + zend_fetch_debug_backtrace(trace, 0, EG(exception_ignore_args) ? DEBUG_BACKTRACE_IGNORE_ARGS : 0, 0); } else { - array_init(&trace); + ZVAL_EMPTY_ARRAY(trace); } - Z_SET_REFCOUNT(trace, 0); - - base_ce = i_get_exception_base(object); if (EXPECTED((class_type != zend_ce_parse_error && class_type != zend_ce_compile_error) || !(filename = zend_get_compiled_filename()))) { - ZVAL_STRING(&tmp, zend_get_executed_filename()); - zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_FILE), &tmp); - zval_ptr_dtor(&tmp); - ZVAL_LONG(&tmp, zend_get_executed_lineno()); - zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_LINE), &tmp); + ZVAL_STRING(zend_obj_prop_num_checked(object, 3, ZSTR_KNOWN(ZEND_STR_FILE)), zend_get_executed_filename()); + ZVAL_LONG(zend_obj_prop_num_checked(object, 4, ZSTR_KNOWN(ZEND_STR_LINE)), zend_get_executed_lineno()); } else { - ZVAL_STR(&tmp, filename); - zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_FILE), &tmp); - ZVAL_LONG(&tmp, zend_get_compiled_lineno()); - zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_LINE), &tmp); + ZVAL_STR_COPY(zend_obj_prop_num_checked(object, 3, ZSTR_KNOWN(ZEND_STR_FILE)), filename); + ZVAL_LONG(zend_obj_prop_num_checked(object, 4, ZSTR_KNOWN(ZEND_STR_LINE)), zend_get_compiled_lineno()); } - zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_TRACE), &trace); return object; } @@ -307,28 +311,30 @@ ZEND_METHOD(Exception, __construct) { zend_string *message = NULL; zend_long code = 0; - zval tmp, *object, *previous = NULL; - zend_class_entry *base_ce; + zval *object, *previous = NULL; object = ZEND_THIS; - base_ce = i_get_exception_base(Z_OBJ_P(object)); if (zend_parse_parameters(ZEND_NUM_ARGS(), "|SlO!", &message, &code, &previous, zend_ce_throwable) == FAILURE) { RETURN_THROWS(); } if (message) { - ZVAL_STR(&tmp, message); - zend_update_property_ex(base_ce, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_MESSAGE), &tmp); + zval *tmp = zend_obj_prop_num_checked(Z_OBJ_P(object), 0, ZSTR_KNOWN(ZEND_STR_MESSAGE)); + zval_ptr_dtor(tmp); + ZVAL_STR_COPY(tmp, message); } if (code) { - ZVAL_LONG(&tmp, code); - zend_update_property_ex(base_ce, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_CODE), &tmp); + zval *tmp = zend_obj_prop_num_checked(Z_OBJ_P(object), 2, ZSTR_KNOWN(ZEND_STR_CODE)); + zval_ptr_dtor(tmp); + ZVAL_LONG(tmp, code); } if (previous) { - zend_update_property_ex(base_ce, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_PREVIOUS), previous); + zval *tmp = zend_obj_prop_num_checked(Z_OBJ_P(object), 6, ZSTR_KNOWN(ZEND_STR_PREVIOUS)); + zval_ptr_dtor(tmp); + ZVAL_COPY(tmp, previous); } } /* }}} */ @@ -358,7 +364,7 @@ ZEND_METHOD(ErrorException, __construct) zend_string *message = NULL, *filename = NULL; zend_long code = 0, severity = E_ERROR, lineno; bool lineno_is_null = 1; - zval tmp, *object, *previous = NULL; + zval *object, *previous = NULL; if (zend_parse_parameters(ZEND_NUM_ARGS(), "|SllS!l!O!", &message, &code, &severity, &filename, &lineno, &lineno_is_null, &previous, zend_ce_throwable) == FAILURE) { RETURN_THROWS(); @@ -367,35 +373,41 @@ ZEND_METHOD(ErrorException, __construct) object = ZEND_THIS; if (message) { - ZVAL_STR_COPY(&tmp, message); - zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_MESSAGE), &tmp); - zval_ptr_dtor(&tmp); + zval *tmp = zend_obj_prop_num_checked(Z_OBJ_P(object), 0, ZSTR_KNOWN(ZEND_STR_MESSAGE)); + zval_ptr_dtor(tmp); + ZVAL_STR_COPY(tmp, message); } if (code) { - ZVAL_LONG(&tmp, code); - zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_CODE), &tmp); + zval *tmp = zend_obj_prop_num_checked(Z_OBJ_P(object), 2, ZSTR_KNOWN(ZEND_STR_CODE)); + zval_ptr_dtor(tmp); + ZVAL_LONG(tmp, code); } if (previous) { - zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_PREVIOUS), previous); + zval *tmp = zend_obj_prop_num_checked(Z_OBJ_P(object), 6, ZSTR_KNOWN(ZEND_STR_PREVIOUS)); + zval_ptr_dtor(tmp); + ZVAL_COPY(tmp, previous); } - ZVAL_LONG(&tmp, severity); - zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_SEVERITY), &tmp); + { + zval *tmp = zend_obj_prop_num_checked(Z_OBJ_P(object), 7, ZSTR_KNOWN(ZEND_STR_SEVERITY)); + zval_ptr_dtor(tmp); + ZVAL_LONG(tmp, severity); + } if (filename) { - ZVAL_STR_COPY(&tmp, filename); - zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_FILE), &tmp); - zval_ptr_dtor(&tmp); + zval *tmp = zend_obj_prop_num_checked(Z_OBJ_P(object), 3, ZSTR_KNOWN(ZEND_STR_FILE)); + zval_ptr_dtor(tmp); + ZVAL_STR_COPY(tmp, filename); } + zval *tmp = zend_obj_prop_num_checked(Z_OBJ_P(object), 4, ZSTR_KNOWN(ZEND_STR_LINE)); + zval_ptr_dtor(tmp); if (!lineno_is_null) { - ZVAL_LONG(&tmp, lineno); - zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_LINE), &tmp); + ZVAL_LONG(tmp, lineno); } else if (filename) { - ZVAL_LONG(&tmp, 0); - zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_LINE), &tmp); + ZVAL_LONG(tmp, 0); } } /* }}} */