[php-src] PR #23517: PHP array_shift(): Optimize tail move on hole-free packed arrays
| From: | mehmetcansahin | Date: | Sun, 30 Aug 2026 21:44:42 +0000 |
| Subject: | [php-src] PR #23517: PHP array_shift(): Optimize tail move on hole-free packed arrays | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-38227@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/23517
Author: mehmetcansahin
For a packed array without holes, removing the first element leaves a
contiguous tail. When no iterator is active,
array_shift() now moves that
tail with a single memmove() instead of testing every slot for IS_UNDEF;
a one-element tail is moved directly to avoid the call overhead. Packed
arrays with holes, arrays with active iterators, and hash arrays keep the
existing paths.
### Benchmarks
Apple M1, Apple clang 21.0.0, ./configure --disable-all --enable-cli
(NTS release), php -n, best of three in-process samples. Base
f142b815884, this PR fc910a396ea. Full scripts below.
Draining a packed int array ($a = $base; while ($a) array_shift($a);):
| n | repetitions | base | this PR | speedup |
|---:|------------:|-----:|--------:|--------:|
| 100 | 20,000 | 160.5 ms | 58.7 ms | 2.7× |
| 1,000 | 2,000 | 1,075.6 ms | 249.9 ms | 4.3× |
| 10,000 | 200 | 10,726.5 ms | 2,602.4 ms | 4.1× |
Two-element regression check ($a = [0, 1]; array_shift($a); x 100M,
exercises the direct-copy specialization): 3.528 s → 3.438 s, no tiny-array
slowdown observed.
<details>
<summary>Benchmark scripts</summary>
Draining benchmark:
```php
<?php
foreach ([100, 1000, 10000] as $n) {
$reps = intdiv(2000000, $n);
$base = range(0, $n - 1);
$best = PHP_FLOAT_MAX;
for ($run = 0; $run < 3; $run++) {
$t = hrtime(true);
for ($r = 0; $r < $reps; $r++) {
$a = $base;
while ($a) array_shift($a);
}
$best = min($best, (hrtime(true) - $t) / 1e6);
}
printf("n=%-6d drain x%-5d best: %8.1f ms\n", $n, $reps, $best);
}
```
Two-element regression check:
```php
<?php
$best = PHP_FLOAT_MAX;
for ($run = 0; $run < 3; $run++) {
$t = hrtime(true);
for ($i = 0; $i < 100000000; $i++) {
$a = [0, 1];
array_shift($a);
}
$best = min($best, (hrtime(true) - $t) / 1e9);
}
printf("fresh [0,1] x100M best: %.3f s\n", $best);
```
</details>
### Tests
ext/standard/tests/array/: 856 PASS, 12 SKIP, 0 FAIL/WARN on both the
release NTS build and a Debug+ZTS build (hashtable consistency assertions
enabled). The new test covers packed arrays with holes, which keep the
slot-testing loop.
> Parts of this description were drafted with LLM assistance and reviewed by
> the author, per the "LLM usage in GitHub comments" section of
> CONTRIBUTING.md. All results were produced and verified locally.