From 335c0b39a24c83e4d7c6ea028123d5927e900c63 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 30 Mar 2025 13:16:33 +0200 Subject: [PATCH 1/6] Optimize SplFixedArray dimension performance (#18184) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch optimizes reading and writing from SplFixedArray with the dimension operators. It accomplishes this due to the following optimizations: * Fast-path for long keys (inlined). * Optimization hints (UNEXPECTED + assertion) * Using an unsigned index so we can do a single length comparison For the following script: ```php $test = new SplFixedArray(4); for ($i = 0 ; $i< 5000000; $i++) $test[1] += $i; ``` On an i7-4790: ``` Benchmark 1: ./sapi/cli/php x.php Time (mean ± σ): 95.4 ms ± 1.6 ms [User: 91.5 ms, System: 3.2 ms] Range (min … max): 93.7 ms … 100.8 ms 31 runs Benchmark 2: ./sapi/cli/php_old x.php Time (mean ± σ): 119.1 ms ± 1.3 ms [User: 114.7 ms, System: 3.6 ms] Range (min … max): 117.6 ms … 123.1 ms 24 runs Summary ./sapi/cli/php x.php ran 1.25 ± 0.03 times faster than ./sapi/cli/php_old x.php ``` On an i7-1185G7: ``` Benchmark 1: ./sapi/cli/php x.php Time (mean ± σ): 67.9 ms ± 1.1 ms [User: 64.8 ms, System: 3.2 ms] Range (min … max): 66.6 ms … 72.8 ms 43 runs Benchmark 2: ./sapi/cli/php_old x.php Time (mean ± σ): 84.8 ms ± 1.1 ms [User: 81.0 ms, System: 3.9 ms] Range (min … max): 82.6 ms … 88.0 ms 34 runs Summary ./sapi/cli/php x.php ran 1.25 ± 0.03 times faster than ./sapi/cli/php_old x.php ``` --- ext/spl/spl_fixedarray.c | 52 +++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/ext/spl/spl_fixedarray.c b/ext/spl/spl_fixedarray.c index b919501c0dd25..fa846e48eee03 100644 --- a/ext/spl/spl_fixedarray.c +++ b/ext/spl/spl_fixedarray.c @@ -324,14 +324,14 @@ static zend_object *spl_fixedarray_object_clone(zend_object *old_object) return new_object; } -static zend_long spl_offset_convert_to_long(zval *offset) /* {{{ */ +static zend_never_inline zend_ulong spl_offset_convert_to_ulong_slow(const zval *offset) /* {{{ */ { try_again: switch (Z_TYPE_P(offset)) { case IS_STRING: { zend_ulong index; if (ZEND_HANDLE_NUMERIC(Z_STR_P(offset), index)) { - return (zend_long) index; + return index; } break; } @@ -356,10 +356,22 @@ static zend_long spl_offset_convert_to_long(zval *offset) /* {{{ */ return 0; } -static zval *spl_fixedarray_object_read_dimension_helper(spl_fixedarray_object *intern, zval *offset) +/* Returned index is an unsigned number such that we don't have to do a negative check. + * Negative numbers will be mapped at indices larger than ZEND_ULONG_MAX, + * which is beyond the maximum length. */ +static zend_always_inline zend_ulong spl_offset_convert_to_ulong(const zval *offset) { - zend_long index; + if (EXPECTED(Z_TYPE_P(offset) == IS_LONG)) { + /* Allow skipping exception check at call-site. */ + ZEND_ASSERT(!EG(exception)); + return Z_LVAL_P(offset); + } else { + return spl_offset_convert_to_ulong_slow(offset); + } +} +static zval *spl_fixedarray_object_read_dimension_helper(spl_fixedarray_object *intern, zval *offset) +{ /* we have to return NULL on error here to avoid memleak because of * ZE duplicating uninitialized_zval_ptr */ if (!offset) { @@ -367,12 +379,12 @@ static zval *spl_fixedarray_object_read_dimension_helper(spl_fixedarray_object * return NULL; } - index = spl_offset_convert_to_long(offset); - if (EG(exception)) { + zend_ulong index = spl_offset_convert_to_ulong(offset); + if (UNEXPECTED(EG(exception))) { return NULL; } - if (index < 0 || index >= intern->array.size) { + if (UNEXPECTED(index >= intern->array.size)) { zend_throw_exception(spl_ce_OutOfBoundsException, "Index invalid or out of range", 0); return NULL; } else { @@ -407,22 +419,19 @@ static zval *spl_fixedarray_object_read_dimension(zend_object *object, zval *off static void spl_fixedarray_object_write_dimension_helper(spl_fixedarray_object *intern, zval *offset, zval *value) { - zend_long index; - if (!offset) { /* '$array[] = value' syntax is not supported */ zend_throw_error(NULL, "[] operator not supported for SplFixedArray"); return; } - index = spl_offset_convert_to_long(offset); - if (EG(exception)) { + zend_ulong index = spl_offset_convert_to_ulong(offset); + if (UNEXPECTED(EG(exception))) { return; } - if (index < 0 || index >= intern->array.size) { + if (UNEXPECTED(index >= intern->array.size)) { zend_throw_exception(spl_ce_OutOfBoundsException, "Index invalid or out of range", 0); - return; } else { /* Fix #81429 */ zval *ptr = &(intern->array.elements[index]); @@ -452,16 +461,13 @@ static void spl_fixedarray_object_write_dimension(zend_object *object, zval *off static void spl_fixedarray_object_unset_dimension_helper(spl_fixedarray_object *intern, zval *offset) { - zend_long index; - - index = spl_offset_convert_to_long(offset); - if (EG(exception)) { + zend_ulong index = spl_offset_convert_to_ulong(offset); + if (UNEXPECTED(EG(exception))) { return; } - if (index < 0 || index >= intern->array.size) { + if (UNEXPECTED(index >= intern->array.size)) { zend_throw_exception(spl_ce_OutOfBoundsException, "Index invalid or out of range", 0); - return; } else { zval garbage; ZVAL_COPY_VALUE(&garbage, &intern->array.elements[index]); @@ -483,14 +489,12 @@ static void spl_fixedarray_object_unset_dimension(zend_object *object, zval *off static bool spl_fixedarray_object_has_dimension_helper(spl_fixedarray_object *intern, zval *offset, bool check_empty) { - zend_long index; - - index = spl_offset_convert_to_long(offset); - if (EG(exception)) { + zend_ulong index = spl_offset_convert_to_ulong(offset); + if (UNEXPECTED(EG(exception))) { return false; } - if (index < 0 || index >= intern->array.size) { + if (index >= intern->array.size) { return false; } From 07470c3dd06e4090615c7b329a199b0b0dc3352f Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Sun, 30 Mar 2025 15:38:10 +0100 Subject: [PATCH 2/6] ext/pgsql: fix pg_close_stmt() signature. (#18194) --- ext/pgsql/pgsql.stub.php | 2 +- ext/pgsql/pgsql_arginfo.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/pgsql/pgsql.stub.php b/ext/pgsql/pgsql.stub.php index 22177fec367b3..cac22e3a30301 100644 --- a/ext/pgsql/pgsql.stub.php +++ b/ext/pgsql/pgsql.stub.php @@ -971,7 +971,7 @@ function pg_socket_poll($socket, int $read, int $write, int $timeout = -1): int function pg_set_chunked_rows_size(PgSql\Connection $connection, int $size): bool {} #endif #ifdef HAVE_PG_CLOSE_STMT - function pg_close_stmt(Pgsql\Connection $connection, string $statement_name): Pgsql\Result|false {} + function pg_close_stmt(Pgsql\Connection $connection, string $statement_name): PgSql\Result|false {} #endif } diff --git a/ext/pgsql/pgsql_arginfo.h b/ext/pgsql/pgsql_arginfo.h index cb79f83971301..336dd7ff86d04 100644 --- a/ext/pgsql/pgsql_arginfo.h +++ b/ext/pgsql/pgsql_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 13be2a3c9a4ef4a72c0a67019b7400418752b603 */ + * Stub hash: 49e3493be11a5da1ed9a57339f14f92f34bf5d1b */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_pg_connect, 0, 1, PgSql\\Connection, MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, connection_string, IS_STRING, 0) @@ -496,7 +496,7 @@ ZEND_END_ARG_INFO() #endif #if defined(HAVE_PG_CLOSE_STMT) -ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_pg_close_stmt, 0, 2, Pgsql\\Result, MAY_BE_FALSE) +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_pg_close_stmt, 0, 2, PgSql\\Result, MAY_BE_FALSE) ZEND_ARG_OBJ_INFO(0, connection, Pgsql\\Connection, 0) ZEND_ARG_TYPE_INFO(0, statement_name, IS_STRING, 0) ZEND_END_ARG_INFO() From d13d9b3c24ffeedaa114313eb184ea61323867f3 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 30 Mar 2025 18:09:01 +0200 Subject: [PATCH 3/6] Optimize SplFixedArray::toArray() (#18190) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We can use the optimized packed filling code instead of going through all the logic of the zend_hash update APIs. For this script: ```php $test = new SplFixedArray(4); $test[0] = 0; $test[1] = 1; $test[2] = 2; $test[3] = 3; for ($i = 0 ; $i< 5000000; $i++) $test->toArray(); ``` On an i7-4790: ``` Benchmark 1: ./sapi/cli/php toarray.php Time (mean ± σ): 170.0 ms ± 1.8 ms [User: 167.3 ms, System: 2.2 ms] Range (min … max): 166.9 ms … 173.0 ms 17 runs Benchmark 2: ./sapi/cli/php_old toarray.php Time (mean ± σ): 215.7 ms ± 3.6 ms [User: 211.9 ms, System: 3.0 ms] Range (min … max): 211.3 ms … 222.0 ms 13 runs Summary ./sapi/cli/php toarray.php ran 1.27 ± 0.02 times faster than ./sapi/cli/php_old toarray.php ``` On an i7-1185G7: ``` Benchmark 1: ./sapi/cli/php toarray.php Time (mean ± σ): 112.6 ms ± 1.4 ms [User: 109.6 ms, System: 2.9 ms] Range (min … max): 111.1 ms … 116.4 ms 25 runs Benchmark 2: ./sapi/cli/php_old toarray.php Time (mean ± σ): 145.3 ms ± 2.8 ms [User: 141.8 ms, System: 3.4 ms] Range (min … max): 142.6 ms … 151.8 ms 20 runs Summary ./sapi/cli/php toarray.php ran 1.29 ± 0.03 times faster than ./sapi/cli/php_old toarray.php ``` --- ext/spl/spl_fixedarray.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ext/spl/spl_fixedarray.c b/ext/spl/spl_fixedarray.c index fa846e48eee03..b0de1dc483626 100644 --- a/ext/spl/spl_fixedarray.c +++ b/ext/spl/spl_fixedarray.c @@ -695,11 +695,16 @@ PHP_METHOD(SplFixedArray, toArray) intern = Z_SPLFIXEDARRAY_P(ZEND_THIS); if (!spl_fixedarray_empty(&intern->array)) { - array_init(return_value); - for (zend_long i = 0; i < intern->array.size; i++) { - zend_hash_index_update(Z_ARRVAL_P(return_value), i, &intern->array.elements[i]); - Z_TRY_ADDREF(intern->array.elements[i]); - } + array_init_size(return_value, intern->array.size); + HashTable *ht = Z_ARRVAL_P(return_value); + zend_hash_real_init_packed(ht); + + ZEND_HASH_FILL_PACKED(ht) { + for (zend_long i = 0; i < intern->array.size; i++) { + ZEND_HASH_FILL_ADD(&intern->array.elements[i]); + Z_TRY_ADDREF(intern->array.elements[i]); + } + } ZEND_HASH_FILL_END(); } else { RETURN_EMPTY_ARRAY(); } From ce5d2f6d01f6152120c61d47b77ca45c89be2388 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 30 Mar 2025 18:09:11 +0200 Subject: [PATCH 4/6] Make SplFixedArray::jsonSerialize() an implementation alias of SplFixedArray::toArray() (#18191) This reduces code duplication and can then use the optimized version. --- ext/spl/spl_fixedarray.c | 12 ------------ ext/spl/spl_fixedarray.stub.php | 3 +++ ext/spl/spl_fixedarray_arginfo.h | 5 ++--- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/ext/spl/spl_fixedarray.c b/ext/spl/spl_fixedarray.c index b0de1dc483626..4efce87b1a9cd 100644 --- a/ext/spl/spl_fixedarray.c +++ b/ext/spl/spl_fixedarray.c @@ -881,18 +881,6 @@ PHP_METHOD(SplFixedArray, getIterator) zend_create_internal_iterator_zval(return_value, ZEND_THIS); } -PHP_METHOD(SplFixedArray, jsonSerialize) -{ - ZEND_PARSE_PARAMETERS_NONE(); - - spl_fixedarray_object *intern = Z_SPLFIXEDARRAY_P(ZEND_THIS); - array_init_size(return_value, intern->array.size); - for (zend_long i = 0; i < intern->array.size; i++) { - zend_hash_next_index_insert_new(Z_ARR_P(return_value), &intern->array.elements[i]); - Z_TRY_ADDREF(intern->array.elements[i]); - } -} - static void spl_fixedarray_it_dtor(zend_object_iterator *iter) { zval_ptr_dtor(&iter->data); diff --git a/ext/spl/spl_fixedarray.stub.php b/ext/spl/spl_fixedarray.stub.php index a5a239ab4d69c..aa8aa5dbed188 100644 --- a/ext/spl/spl_fixedarray.stub.php +++ b/ext/spl/spl_fixedarray.stub.php @@ -55,5 +55,8 @@ public function offsetUnset($index): void {} public function getIterator(): Iterator {} + /** + * @implementation-alias SplFixedArray::toArray + */ public function jsonSerialize(): array {} } diff --git a/ext/spl/spl_fixedarray_arginfo.h b/ext/spl/spl_fixedarray_arginfo.h index 5d83183d91b81..c2ff9a77ac40f 100644 --- a/ext/spl/spl_fixedarray_arginfo.h +++ b/ext/spl/spl_fixedarray_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: c01c9337e58601ff9e6c85072a62f68cc7fec9ba */ + * Stub hash: 0c838fed60b29671fe04e63315ab662d8cb16f0c */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SplFixedArray___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, size, IS_LONG, 0, "0") @@ -68,7 +68,6 @@ ZEND_METHOD(SplFixedArray, offsetGet); ZEND_METHOD(SplFixedArray, offsetSet); ZEND_METHOD(SplFixedArray, offsetUnset); ZEND_METHOD(SplFixedArray, getIterator); -ZEND_METHOD(SplFixedArray, jsonSerialize); static const zend_function_entry class_SplFixedArray_methods[] = { ZEND_ME(SplFixedArray, __construct, arginfo_class_SplFixedArray___construct, ZEND_ACC_PUBLIC) @@ -85,7 +84,7 @@ static const zend_function_entry class_SplFixedArray_methods[] = { ZEND_ME(SplFixedArray, offsetSet, arginfo_class_SplFixedArray_offsetSet, ZEND_ACC_PUBLIC) ZEND_ME(SplFixedArray, offsetUnset, arginfo_class_SplFixedArray_offsetUnset, ZEND_ACC_PUBLIC) ZEND_ME(SplFixedArray, getIterator, arginfo_class_SplFixedArray_getIterator, ZEND_ACC_PUBLIC) - ZEND_ME(SplFixedArray, jsonSerialize, arginfo_class_SplFixedArray_jsonSerialize, ZEND_ACC_PUBLIC) + ZEND_RAW_FENTRY("jsonSerialize", zim_SplFixedArray_toArray, arginfo_class_SplFixedArray_jsonSerialize, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_FE_END }; From 24fbe2d61eb6ce3c9aaf2154e711b59f8010b61a Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 30 Mar 2025 18:09:21 +0200 Subject: [PATCH 5/6] Micro-optimizations to str_increment() and str_decrement() (#18193) Since it's a new string we're returning we can use RETURN_NEW_STR() and we can also use zend_string_efree() for the strings that we replace because they have RC1. --- ext/standard/string.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ext/standard/string.c b/ext/standard/string.c index b15a24a098faa..1e20791eb61ce 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -1273,10 +1273,10 @@ PHP_FUNCTION(str_increment) ZSTR_VAL(tmp)[0] = ZSTR_VAL(incremented)[0]; break; } - zend_string_release_ex(incremented, /* persistent */ false); - RETURN_STR(tmp); + zend_string_efree(incremented); + RETURN_NEW_STR(tmp); } - RETURN_STR(incremented); + RETURN_NEW_STR(incremented); } @@ -1323,17 +1323,17 @@ PHP_FUNCTION(str_decrement) if (UNEXPECTED(carry || (ZSTR_VAL(decremented)[0] == '0' && ZSTR_LEN(decremented) > 1))) { if (ZSTR_LEN(decremented) == 1) { - zend_string_release_ex(decremented, /* persistent */ false); + zend_string_efree(decremented); zend_argument_value_error(1, "\"%s\" is out of decrement range", ZSTR_VAL(str)); RETURN_THROWS(); } zend_string *tmp = zend_string_alloc(ZSTR_LEN(decremented) - 1, 0); memcpy(ZSTR_VAL(tmp), ZSTR_VAL(decremented) + 1, ZSTR_LEN(decremented) - 1); ZSTR_VAL(tmp)[ZSTR_LEN(decremented) - 1] = '\0'; - zend_string_release_ex(decremented, /* persistent */ false); - RETURN_STR(tmp); + zend_string_efree(decremented); + RETURN_NEW_STR(tmp); } - RETURN_STR(decremented); + RETURN_NEW_STR(decremented); } #if defined(PHP_WIN32) From f056636086241fadd231751aef66f941a64109e7 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sun, 30 Mar 2025 18:36:38 +0200 Subject: [PATCH 6/6] Avoid rebuilding the property table when possible in SplFixedArray's gc handler (#18195) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If there is not yet a dynamic property, and there are no class properties, then we know that we don't have to build a properties table. For this (micro-bench) script: ```php function x() { $fa = new SplFixedArray(1); $fa[0] = $fa; } for ($i=0;$i<1000000;$i++) x(); ``` On an i7-4790: ``` Benchmark 1: ./sapi/cli/php spl.php Time (mean ± σ): 140.9 ms ± 1.2 ms [User: 137.5 ms, System: 2.7 ms] Range (min … max): 138.9 ms … 144.9 ms 21 runs Benchmark 2: ./sapi/cli/php_old spl.php Time (mean ± σ): 162.0 ms ± 3.8 ms [User: 157.7 ms, System: 3.2 ms] Range (min … max): 158.5 ms … 175.0 ms 17 runs Summary ./sapi/cli/php spl.php ran 1.15 ± 0.03 times faster than ./sapi/cli/php_old spl.php ``` --- ext/spl/spl_fixedarray.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ext/spl/spl_fixedarray.c b/ext/spl/spl_fixedarray.c index 4efce87b1a9cd..b187949699087 100644 --- a/ext/spl/spl_fixedarray.c +++ b/ext/spl/spl_fixedarray.c @@ -212,12 +212,15 @@ static void spl_fixedarray_resize(spl_fixedarray *array, zend_long size) static HashTable* spl_fixedarray_object_get_gc(zend_object *obj, zval **table, int *n) { spl_fixedarray_object *intern = spl_fixed_array_from_obj(obj); - HashTable *ht = zend_std_get_properties(obj); *table = intern->array.elements; *n = (int)intern->array.size; - return ht; + if (obj->properties == NULL && obj->ce->default_properties_count == 0) { + return NULL; + } else { + return zend_std_get_properties(obj); + } } static HashTable* spl_fixedarray_object_get_properties_for(zend_object *obj, zend_prop_purpose purpose)