1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 08:12:21 +01:00

standard: Optimize str_split() (#20419)

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.
This commit is contained in:
Niels Dossche
2025-11-08 23:02:00 +01:00
committed by GitHub
parent 376fdd7afa
commit a09cc6ede6
2 changed files with 17 additions and 8 deletions

View File

@@ -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().

View File

@@ -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();
}
/* }}} */