Skip to content

Commit 55f0b4d

Browse files
committed
Improve performance of instantiating exceptions/errors
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 383aad8 commit 55f0b4d

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)