From 2331ec1153b435634a8b2c09661aea235f94349b Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:14:50 +0100 Subject: [PATCH 1/6] array_map: Avoid needing refcounted copies and cleanup Since this will be copied on the call frame, a refcounted copy is not necessary. --- ext/standard/array.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index 605c21b2012c..128ed682e852 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -6927,7 +6927,7 @@ PHP_FUNCTION(array_map) ZVAL_NULL(¶ms[i]); break; } else if (Z_TYPE(Z_ARRVAL(arrays[i])->arPacked[pos]) != IS_UNDEF) { - ZVAL_COPY(¶ms[i], &Z_ARRVAL(arrays[i])->arPacked[pos]); + ZVAL_COPY_VALUE(¶ms[i], &Z_ARRVAL(arrays[i])->arPacked[pos]); array_pos[i] = pos + 1; break; } @@ -6939,7 +6939,7 @@ PHP_FUNCTION(array_map) ZVAL_NULL(¶ms[i]); break; } else if (Z_TYPE(Z_ARRVAL(arrays[i])->arData[pos].val) != IS_UNDEF) { - ZVAL_COPY(¶ms[i], &Z_ARRVAL(arrays[i])->arData[pos].val); + ZVAL_COPY_VALUE(¶ms[i], &Z_ARRVAL(arrays[i])->arData[pos].val); array_pos[i] = pos + 1; break; } @@ -6959,15 +6959,8 @@ PHP_FUNCTION(array_map) if (Z_TYPE(result) == IS_UNDEF) { efree(array_pos); zend_array_destroy(Z_ARR_P(return_value)); - for (i = 0; i < n_arrays; i++) { - zval_ptr_dtor(¶ms[i]); - } efree(params); RETURN_NULL(); - } else { - for (i = 0; i < n_arrays; i++) { - zval_ptr_dtor(¶ms[i]); - } } zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &result); From f23792c73a322dc4bf5e2e6dc995113c7b0c8d52 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:17:12 +0100 Subject: [PATCH 2/6] array_map: Move fci configuration outside of loop --- ext/standard/array.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index 128ed682e852..e9b13ade6f30 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -6915,6 +6915,10 @@ PHP_FUNCTION(array_map) } else { zval *params = (zval *)safe_emalloc(n_arrays, sizeof(zval), 0); + fci.retval = &result; + fci.param_count = n_arrays; + fci.params = params; + /* We iterate through all the arrays at once. */ for (k = 0; k < maxlen; k++) { for (i = 0; i < n_arrays; i++) { @@ -6948,10 +6952,6 @@ PHP_FUNCTION(array_map) } } - fci.retval = &result; - fci.param_count = n_arrays; - fci.params = params; - zend_result ret = zend_call_function(&fci, &fci_cache); ZEND_ASSERT(ret == SUCCESS); ZEND_IGNORE_VALUE(ret); From dbce0334be40ac414c2000adf25f78486aa5b485 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:18:09 +0100 Subject: [PATCH 3/6] array_map: Delay allocation of array_pos --- ext/standard/array.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index e9b13ade6f30..08514847dd3b 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -6853,12 +6853,9 @@ PHP_FUNCTION(array_map) } ZEND_HASH_FOREACH_END(); } } else { - uint32_t *array_pos = (HashPosition *)ecalloc(n_arrays, sizeof(HashPosition)); - for (i = 0; i < n_arrays; i++) { if (Z_TYPE(arrays[i]) != IS_ARRAY) { zend_argument_type_error(i + 2, "must be of type array, %s given", zend_zval_value_name(&arrays[i])); - efree(array_pos); RETURN_THROWS(); } if (zend_hash_num_elements(Z_ARRVAL(arrays[i])) > maxlen) { @@ -6866,6 +6863,7 @@ PHP_FUNCTION(array_map) } } + uint32_t *array_pos = ecalloc(n_arrays, sizeof(HashPosition)); array_init_size(return_value, maxlen); if (!ZEND_FCI_INITIALIZED(fci)) { From c349660091667b31f22f822a471d14fe781eea08 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:24:36 +0100 Subject: [PATCH 4/6] array_map: Rely on VM to clean up in case of an exception This makes the code more compact, and less weird: the stub doesn't indicate that this function can return NULL. --- ext/standard/array.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index 08514847dd3b..de6c13e455ba 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -6818,8 +6818,7 @@ PHP_FUNCTION(array_map) ZEND_IGNORE_VALUE(ret); if (UNEXPECTED(Z_ISUNDEF(result))) { ZEND_HASH_FILL_FINISH(); - zend_array_destroy(output); - RETURN_NULL(); + RETURN_THROWS(); } } else { ZVAL_UNDEF(&result); @@ -6842,8 +6841,7 @@ PHP_FUNCTION(array_map) ZEND_ASSERT(ret == SUCCESS); ZEND_IGNORE_VALUE(ret); if (UNEXPECTED(Z_ISUNDEF(result))) { - zend_array_destroy(output); - RETURN_NULL(); + RETURN_THROWS(); } if (str_key) { _zend_hash_append(output, str_key, &result); @@ -6956,9 +6954,8 @@ PHP_FUNCTION(array_map) if (Z_TYPE(result) == IS_UNDEF) { efree(array_pos); - zend_array_destroy(Z_ARR_P(return_value)); efree(params); - RETURN_NULL(); + RETURN_THROWS(); } zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &result); From 2b4582e79db08b04d8917614c058bbe5ea4816e2 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Fri, 31 Oct 2025 22:25:04 +0100 Subject: [PATCH 5/6] array_map: Avoid allocation by using Z_EXTRA storage of parameters --- ext/standard/array.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/ext/standard/array.c b/ext/standard/array.c index de6c13e455ba..0fdd3ce6a9ab 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -6772,11 +6772,11 @@ PHP_FUNCTION(array_all) PHP_FUNCTION(array_map) { zval *arrays = NULL; - int n_arrays = 0; + uint32_t n_arrays = 0; zval result; zend_fcall_info fci; zend_fcall_info_cache fci_cache; - int i; + uint32_t i; uint32_t k, maxlen = 0; ZEND_PARSE_PARAMETERS_START(2, -1) @@ -6861,10 +6861,10 @@ PHP_FUNCTION(array_map) } } - uint32_t *array_pos = ecalloc(n_arrays, sizeof(HashPosition)); array_init_size(return_value, maxlen); if (!ZEND_FCI_INITIALIZED(fci)) { + uint32_t *array_pos = ecalloc(n_arrays, sizeof(HashPosition)); zval zv; /* We iterate through all the arrays at once. */ @@ -6908,9 +6908,16 @@ PHP_FUNCTION(array_map) zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &result); } + + efree(array_pos); } else { zval *params = (zval *)safe_emalloc(n_arrays, sizeof(zval), 0); + /* Remember next starting point in the array, initialize those as zeros. */ + for (i = 0; i < n_arrays; i++) { + Z_EXTRA(params[i]) = 0; + } + fci.retval = &result; fci.param_count = n_arrays; fci.params = params; @@ -6920,7 +6927,7 @@ PHP_FUNCTION(array_map) for (i = 0; i < n_arrays; i++) { /* If this array still has elements, add the current one to the * parameter list, otherwise use null value. */ - uint32_t pos = array_pos[i]; + uint32_t pos = Z_EXTRA(params[i]); if (HT_IS_PACKED(Z_ARRVAL(arrays[i]))) { while (1) { if (pos >= Z_ARRVAL(arrays[i])->nNumUsed) { @@ -6928,7 +6935,7 @@ PHP_FUNCTION(array_map) break; } else if (Z_TYPE(Z_ARRVAL(arrays[i])->arPacked[pos]) != IS_UNDEF) { ZVAL_COPY_VALUE(¶ms[i], &Z_ARRVAL(arrays[i])->arPacked[pos]); - array_pos[i] = pos + 1; + Z_EXTRA(params[i]) = pos + 1; break; } pos++; @@ -6940,7 +6947,7 @@ PHP_FUNCTION(array_map) break; } else if (Z_TYPE(Z_ARRVAL(arrays[i])->arData[pos].val) != IS_UNDEF) { ZVAL_COPY_VALUE(¶ms[i], &Z_ARRVAL(arrays[i])->arData[pos].val); - array_pos[i] = pos + 1; + Z_EXTRA(params[i]) = pos + 1; break; } pos++; @@ -6953,7 +6960,6 @@ PHP_FUNCTION(array_map) ZEND_IGNORE_VALUE(ret); if (Z_TYPE(result) == IS_UNDEF) { - efree(array_pos); efree(params); RETURN_THROWS(); } @@ -6963,7 +6969,6 @@ PHP_FUNCTION(array_map) efree(params); } - efree(array_pos); } } /* }}} */ From 9664d4d73d9ee378a8c4d4c1b76aa16dbbd85d68 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Sat, 8 Nov 2025 18:11:12 +0100 Subject: [PATCH 6/6] Update UPGRADING for array_map() performance --- UPGRADING | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADING b/UPGRADING index ddef86c1ae37..2278562f9caa 100644 --- a/UPGRADING +++ b/UPGRADING @@ -125,6 +125,7 @@ PHP 8.6 UPGRADE NOTES - Standard: . Improved performance of array_fill_keys(). + . Improved performance of array_map() with multiple arrays passed. . Improved performance of array_unshift(). . Improved performance of array_walk(). . Improved performance of intval('+0b...', 2) and intval('0b...', 2).