From f5463fa0b3f1ea4b55113631c528b4c28cc071a8 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Wed, 26 Mar 2025 23:29:47 +0100 Subject: [PATCH 1/2] Improve performance of array_map() The refcounting and destruction is not necessary because zend_call_function will make a copy anyway. And zend_call_function only returns FAILURE if EG(active) is false in which case array_map shouldn't have been called in the first place. --- ext/standard/array.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index 8462492651310..fd0db782267ae 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -6738,8 +6738,6 @@ PHP_FUNCTION(array_map) if (n_arrays == 1) { zend_ulong num_key; zend_string *str_key; - zval *zv, arg; - int ret; if (Z_TYPE(arrays[0]) != IS_ARRAY) { zend_argument_type_error(2, "must be of type array, %s given", zend_zval_value_name(&arrays[0])); @@ -6756,15 +6754,14 @@ PHP_FUNCTION(array_map) array_init_size(return_value, maxlen); zend_hash_real_init(Z_ARRVAL_P(return_value), HT_IS_PACKED(Z_ARRVAL(arrays[0]))); - ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL(arrays[0]), num_key, str_key, zv) { - fci.retval = &result; - fci.param_count = 1; - fci.params = &arg; + fci.retval = &result; + fci.param_count = 1; - ZVAL_COPY(&arg, zv); - ret = zend_call_function(&fci, &fci_cache); - i_zval_ptr_dtor(&arg); - if (ret != SUCCESS || Z_TYPE(result) == IS_UNDEF) { + ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL(arrays[0]), num_key, str_key, fci.params) { + zend_result ret = zend_call_function(&fci, &fci_cache); + ZEND_ASSERT(ret == SUCCESS); + ZEND_IGNORE_VALUE(ret); + if (Z_TYPE(result) == IS_UNDEF) { zend_array_destroy(Z_ARR_P(return_value)); RETURN_NULL(); } @@ -6874,7 +6871,11 @@ PHP_FUNCTION(array_map) fci.param_count = n_arrays; fci.params = params; - if (zend_call_function(&fci, &fci_cache) != SUCCESS || Z_TYPE(result) == IS_UNDEF) { + zend_result ret = zend_call_function(&fci, &fci_cache); + ZEND_ASSERT(ret == SUCCESS); + ZEND_IGNORE_VALUE(ret); + + if (Z_TYPE(result) == IS_UNDEF) { efree(array_pos); zend_array_destroy(Z_ARR_P(return_value)); for (i = 0; i < n_arrays; i++) { From 846900b8f9889f1d9d1734c03685a8217f07aa0d Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 27 Mar 2025 21:51:56 +0100 Subject: [PATCH 2/2] Implement packed fast-path for array_map() --- ext/standard/array.c | 74 +++++++++++++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index fd0db782267ae..25b1b6088378f 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -6736,14 +6736,12 @@ PHP_FUNCTION(array_map) ZEND_PARSE_PARAMETERS_END(); if (n_arrays == 1) { - zend_ulong num_key; - zend_string *str_key; - if (Z_TYPE(arrays[0]) != IS_ARRAY) { zend_argument_type_error(2, "must be of type array, %s given", zend_zval_value_name(&arrays[0])); RETURN_THROWS(); } - maxlen = zend_hash_num_elements(Z_ARRVAL(arrays[0])); + const HashTable *input = Z_ARRVAL(arrays[0]); + maxlen = zend_hash_num_elements(input); /* Short-circuit: if no callback and only one array, just return it. */ if (!ZEND_FCI_INITIALIZED(fci) || !maxlen) { @@ -6751,26 +6749,60 @@ PHP_FUNCTION(array_map) return; } - array_init_size(return_value, maxlen); - zend_hash_real_init(Z_ARRVAL_P(return_value), HT_IS_PACKED(Z_ARRVAL(arrays[0]))); - fci.retval = &result; fci.param_count = 1; - ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL(arrays[0]), num_key, str_key, fci.params) { - zend_result ret = zend_call_function(&fci, &fci_cache); - ZEND_ASSERT(ret == SUCCESS); - ZEND_IGNORE_VALUE(ret); - if (Z_TYPE(result) == IS_UNDEF) { - zend_array_destroy(Z_ARR_P(return_value)); - RETURN_NULL(); - } - if (str_key) { - _zend_hash_append(Z_ARRVAL_P(return_value), str_key, &result); - } else { - zend_hash_index_add_new(Z_ARRVAL_P(return_value), num_key, &result); - } - } ZEND_HASH_FOREACH_END(); + if (HT_IS_PACKED(input)) { + array_init_size(return_value, input->nNumUsed); + HashTable *output = Z_ARRVAL_P(return_value); + zend_hash_real_init_packed(output); + + uint32_t undefs = 0; + ZEND_HASH_FILL_PACKED(output) { + /* Can't use ZEND_HASH_PACKED_FOREACH_VAL() because we need to also account for the UNDEF values + * so the keys in the output array will match those of the input array. */ + for (zval *cur = input->arPacked, *end = input->arPacked + input->nNumUsed; cur != end; cur++) { + if (EXPECTED(!Z_ISUNDEF_P(cur))) { + fci.params = cur; + zend_result ret = zend_call_function(&fci, &fci_cache); + ZEND_ASSERT(ret == SUCCESS); + ZEND_IGNORE_VALUE(ret); + if (UNEXPECTED(Z_ISUNDEF(result))) { + ZEND_HASH_FILL_FINISH(); + zend_array_destroy(output); + RETURN_NULL(); + } + } else { + ZVAL_UNDEF(&result); + undefs++; + } + ZEND_HASH_FILL_ADD(&result); + } + } ZEND_HASH_FILL_END(); + output->nNumOfElements -= undefs; + } else { + zend_ulong num_key; + zend_string *str_key; + + array_init_size(return_value, maxlen); + HashTable *output = Z_ARRVAL_P(return_value); + zend_hash_real_init_mixed(output); + + ZEND_HASH_MAP_FOREACH_KEY_VAL(input, num_key, str_key, fci.params) { + zend_result ret = zend_call_function(&fci, &fci_cache); + ZEND_ASSERT(ret == SUCCESS); + ZEND_IGNORE_VALUE(ret); + if (UNEXPECTED(Z_ISUNDEF(result))) { + zend_array_destroy(output); + RETURN_NULL(); + } + if (str_key) { + _zend_hash_append(output, str_key, &result); + } else { + zend_hash_index_add_new(output, num_key, &result); + } + } ZEND_HASH_FOREACH_END(); + } } else { uint32_t *array_pos = (HashPosition *)ecalloc(n_arrays, sizeof(HashPosition));