From a70b65e7a2b4932b9b05a77714e1807ee7e93966 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Fri, 7 Nov 2025 22:55:12 +0100 Subject: [PATCH 1/2] standard: Optimize str_split() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three optimizations: - If the entire string is returned, we don't need to duplicate it. - Use packed filling logic. - Use fast construction of strings. This is useful when splitting strings on length=1. In that case I get a 6x speedup in the code below. Bench: ```php $x = str_repeat('A', 100); for ($i = 0; $i < 1000000; $i++) str_split($x, 10); ``` On an i7-4790: ``` Benchmark 1: ./sapi/cli/php x.php Time (mean ± σ): 160.1 ms ± 6.4 ms [User: 157.3 ms, System: 1.8 ms] Range (min … max): 155.6 ms … 184.7 ms 18 runs Benchmark 2: ./sapi/cli/php_old x.php Time (mean ± σ): 202.6 ms ± 4.0 ms [User: 199.1 ms, System: 1.9 ms] Range (min … max): 197.4 ms … 209.2 ms 14 runs Summary ./sapi/cli/php x.php ran 1.27 ± 0.06 times faster than ./sapi/cli/php_old x.php ``` The performance gain increases with smaller lengths. --- ext/standard/string.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/ext/standard/string.c b/ext/standard/string.c index 6fb39c5a6bd2..ddf4221bf6ed 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -6152,23 +6152,31 @@ PHP_FUNCTION(str_split) } array_init_size(return_value, 1); - add_next_index_stringl(return_value, ZSTR_VAL(str), ZSTR_LEN(str)); + GC_TRY_ADDREF(str); + add_next_index_str(return_value, str); return; } array_init_size(return_value, (uint32_t)(((ZSTR_LEN(str) - 1) / split_length) + 1)); + zend_hash_real_init_packed(Z_ARRVAL_P(return_value)); n_reg_segments = ZSTR_LEN(str) / split_length; p = ZSTR_VAL(str); - while (n_reg_segments-- > 0) { - add_next_index_stringl(return_value, p, split_length); - p += split_length; - } + ZEND_HASH_FILL_PACKED(Z_ARRVAL_P(return_value)) { + zval zv; + while (n_reg_segments-- > 0) { + ZEND_ASSERT(split_length > 0); + ZVAL_STRINGL_FAST(&zv, p, split_length); + ZEND_HASH_FILL_ADD(&zv); + p += split_length; + } - if (p != (ZSTR_VAL(str) + ZSTR_LEN(str))) { - add_next_index_stringl(return_value, p, (ZSTR_VAL(str) + ZSTR_LEN(str) - p)); - } + if (p != (ZSTR_VAL(str) + ZSTR_LEN(str))) { + ZVAL_STRINGL_FAST(&zv, p, (ZSTR_VAL(str) + ZSTR_LEN(str) - p)); + ZEND_HASH_FILL_ADD(&zv); + } + } ZEND_HASH_FILL_END(); } /* }}} */ From 91a570d78a3fccead0cb0ee97dfa6b7b00aa2976 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+ndossche@users.noreply.github.com> Date: Sat, 8 Nov 2025 23:01:26 +0100 Subject: [PATCH 2/2] [ci skip] UPGRADING --- UPGRADING | 1 + 1 file changed, 1 insertion(+) diff --git a/UPGRADING b/UPGRADING index ddef86c1ae37..5da76d924fbf 100644 --- a/UPGRADING +++ b/UPGRADING @@ -128,3 +128,4 @@ PHP 8.6 UPGRADE NOTES . Improved performance of array_unshift(). . Improved performance of array_walk(). . Improved performance of intval('+0b...', 2) and intval('0b...', 2). + . Improved performance of str_split().