Skip to content

Commit 5bbe3d7

Browse files
authored
Improve performance of instantiating exceptions/errors (#18442)
The class structure is fixed, so it makes no sense to go through all the logic of looking up property info etc if there are no hooks. This patch introduces a local function `zend_update_property_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 ```
1 parent 146157d commit 5bbe3d7

File tree

2 files changed

+52
-26
lines changed

2 files changed

+52
-26
lines changed

UPGRADING

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@ PHP 8.5 UPGRADE NOTES
520520
. Remove OPcodes for identity comparisons against booleans, particularly
521521
for the match(true) pattern.
522522
. Add OPcode specialization for `=== []` and `!== []` comparisons.
523+
. Creating exception objects is now much faster.
523524

524525
- ReflectionProperty:
525526
. Improved performance of the following methods: getValue(), getRawValue(),

Zend/zend_exceptions.c

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@
3030
#include "zend_exceptions_arginfo.h"
3131
#include "zend_observer.h"
3232

33+
#define ZEND_EXCEPTION_MESSAGE_OFF 0
34+
#define ZEND_EXCEPTION_CODE_OFF 2
35+
#define ZEND_EXCEPTION_FILE_OFF 3
36+
#define ZEND_EXCEPTION_LINE_OFF 4
37+
#define ZEND_EXCEPTION_TRACE_OFF 5
38+
#define ZEND_EXCEPTION_PREVIOUS_OFF 6
39+
#define ZEND_EXCEPTION_SEVERITY_OFF 7
40+
3341
ZEND_API zend_class_entry *zend_ce_throwable;
3442
ZEND_API zend_class_entry *zend_ce_exception;
3543
ZEND_API zend_class_entry *zend_ce_error_exception;
@@ -254,11 +262,33 @@ ZEND_API void zend_clear_exception(void) /* {{{ */
254262
}
255263
/* }}} */
256264

265+
/* Same as writing to OBJ_PROP_NUM() when there are no hooks,
266+
* but checks the offset is correct when Zend is built in debug mode.
267+
* This is faster than going through the regular property write routine when the offset is known at compile time. */
268+
static void zend_update_property_num_checked(zend_object *object, uint32_t prop_num, zend_string *member, zval *value)
269+
{
270+
if (UNEXPECTED(object->ce->num_hooked_props > 0)) {
271+
/* Property may have been overridden with a hook. */
272+
zend_update_property_ex(object->ce, object, member, value);
273+
zval_ptr_dtor(value);
274+
return;
275+
}
276+
#if ZEND_DEBUG
277+
zend_class_entry *old_scope = EG(fake_scope);
278+
EG(fake_scope) = i_get_exception_base(object);
279+
const zend_property_info *prop_info = zend_get_property_info(object->ce, member, true);
280+
ZEND_ASSERT(OBJ_PROP_TO_NUM(prop_info->offset) == prop_num);
281+
EG(fake_scope) = old_scope;
282+
#endif
283+
zval *zv = OBJ_PROP_NUM(object, prop_num);
284+
zval_ptr_safe_dtor(zv);
285+
ZVAL_COPY_VALUE(zv, value);
286+
}
287+
257288
static zend_object *zend_default_exception_new(zend_class_entry *class_type) /* {{{ */
258289
{
259290
zval tmp;
260291
zval trace;
261-
zend_class_entry *base_ce;
262292
zend_string *filename;
263293

264294
zend_object *object = zend_objects_new(class_type);
@@ -269,26 +299,23 @@ static zend_object *zend_default_exception_new(zend_class_entry *class_type) /*
269299
0,
270300
EG(exception_ignore_args) ? DEBUG_BACKTRACE_IGNORE_ARGS : 0, 0);
271301
} else {
272-
array_init(&trace);
302+
ZVAL_EMPTY_ARRAY(&trace);
273303
}
274-
Z_SET_REFCOUNT(trace, 0);
275304

276-
base_ce = i_get_exception_base(object);
305+
zend_update_property_num_checked(object, ZEND_EXCEPTION_TRACE_OFF, ZSTR_KNOWN(ZEND_STR_TRACE), &trace);
277306

278307
if (EXPECTED((class_type != zend_ce_parse_error && class_type != zend_ce_compile_error)
279308
|| !(filename = zend_get_compiled_filename()))) {
280309
ZVAL_STRING(&tmp, zend_get_executed_filename());
281-
zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_FILE), &tmp);
282-
zval_ptr_dtor(&tmp);
310+
zend_update_property_num_checked(object, ZEND_EXCEPTION_FILE_OFF, ZSTR_KNOWN(ZEND_STR_FILE), &tmp);
283311
ZVAL_LONG(&tmp, zend_get_executed_lineno());
284-
zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_LINE), &tmp);
312+
zend_update_property_num_checked(object, ZEND_EXCEPTION_LINE_OFF, ZSTR_KNOWN(ZEND_STR_LINE), &tmp);
285313
} else {
286-
ZVAL_STR(&tmp, filename);
287-
zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_FILE), &tmp);
314+
ZVAL_STR_COPY(&tmp, filename);
315+
zend_update_property_num_checked(object, ZEND_EXCEPTION_FILE_OFF, ZSTR_KNOWN(ZEND_STR_FILE), &tmp);
288316
ZVAL_LONG(&tmp, zend_get_compiled_lineno());
289-
zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_LINE), &tmp);
317+
zend_update_property_num_checked(object, ZEND_EXCEPTION_LINE_OFF, ZSTR_KNOWN(ZEND_STR_LINE), &tmp);
290318
}
291-
zend_update_property_ex(base_ce, object, ZSTR_KNOWN(ZEND_STR_TRACE), &trace);
292319

293320
return object;
294321
}
@@ -308,27 +335,26 @@ ZEND_METHOD(Exception, __construct)
308335
zend_string *message = NULL;
309336
zend_long code = 0;
310337
zval tmp, *object, *previous = NULL;
311-
zend_class_entry *base_ce;
312338

313339
object = ZEND_THIS;
314-
base_ce = i_get_exception_base(Z_OBJ_P(object));
315340

316341
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|SlO!", &message, &code, &previous, zend_ce_throwable) == FAILURE) {
317342
RETURN_THROWS();
318343
}
319344

320345
if (message) {
321-
ZVAL_STR(&tmp, message);
322-
zend_update_property_ex(base_ce, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_MESSAGE), &tmp);
346+
ZVAL_STR_COPY(&tmp, message);
347+
zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_MESSAGE_OFF, ZSTR_KNOWN(ZEND_STR_MESSAGE), &tmp);
323348
}
324349

325350
if (code) {
326351
ZVAL_LONG(&tmp, code);
327-
zend_update_property_ex(base_ce, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_CODE), &tmp);
352+
zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_CODE_OFF, ZSTR_KNOWN(ZEND_STR_CODE), &tmp);
328353
}
329354

330355
if (previous) {
331-
zend_update_property_ex(base_ce, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_PREVIOUS), previous);
356+
Z_ADDREF_P(previous);
357+
zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_PREVIOUS_OFF, ZSTR_KNOWN(ZEND_STR_PREVIOUS), previous);
332358
}
333359
}
334360
/* }}} */
@@ -368,34 +394,33 @@ ZEND_METHOD(ErrorException, __construct)
368394

369395
if (message) {
370396
ZVAL_STR_COPY(&tmp, message);
371-
zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_MESSAGE), &tmp);
372-
zval_ptr_dtor(&tmp);
397+
zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_MESSAGE_OFF, ZSTR_KNOWN(ZEND_STR_MESSAGE), &tmp);
373398
}
374399

375400
if (code) {
376401
ZVAL_LONG(&tmp, code);
377-
zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_CODE), &tmp);
402+
zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_CODE_OFF, ZSTR_KNOWN(ZEND_STR_CODE), &tmp);
378403
}
379404

380405
if (previous) {
381-
zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_PREVIOUS), previous);
406+
Z_ADDREF_P(previous);
407+
zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_PREVIOUS_OFF, ZSTR_KNOWN(ZEND_STR_PREVIOUS), previous);
382408
}
383409

384410
ZVAL_LONG(&tmp, severity);
385-
zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_SEVERITY), &tmp);
411+
zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_SEVERITY_OFF, ZSTR_KNOWN(ZEND_STR_SEVERITY), &tmp);
386412

387413
if (filename) {
388414
ZVAL_STR_COPY(&tmp, filename);
389-
zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_FILE), &tmp);
390-
zval_ptr_dtor(&tmp);
415+
zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_FILE_OFF, ZSTR_KNOWN(ZEND_STR_FILE), &tmp);
391416
}
392417

393418
if (!lineno_is_null) {
394419
ZVAL_LONG(&tmp, lineno);
395-
zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_LINE), &tmp);
420+
zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_LINE_OFF, ZSTR_KNOWN(ZEND_STR_LINE), &tmp);
396421
} else if (filename) {
397422
ZVAL_LONG(&tmp, 0);
398-
zend_update_property_ex(zend_ce_exception, Z_OBJ_P(object), ZSTR_KNOWN(ZEND_STR_LINE), &tmp);
423+
zend_update_property_num_checked(Z_OBJ_P(object), ZEND_EXCEPTION_LINE_OFF, ZSTR_KNOWN(ZEND_STR_LINE), &tmp);
399424
}
400425
}
401426
/* }}} */

0 commit comments

Comments
 (0)