Skip to content

array_chunk(): Fill packed chunks directly when keys are not preserved - #23556

Open
mehmetcansahin wants to merge 2 commits into
php:masterfrom
mehmetcansahin:array-chunk-fill
Open

array_chunk(): Fill packed chunks directly when keys are not preserved#23556
mehmetcansahin wants to merge 2 commits into
php:masterfrom
mehmetcansahin:array-chunk-fill

Conversation

@mehmetcansahin

@mehmetcansahin mehmetcansahin commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

This PR makes array_chunk() faster when $preserve_keys is false, which is the default.
In this case, each chunk is always a list, so PHP can fill it directly instead of adding the values one by one.
This works for both packed arrays and arrays with string keys, including arrays with removed elements. Reference handling stays the same, and the $preserve_keys = true path is unchanged.
Benchmarks show a 2.1×–2.8× speedup.

Benchmarks

array_chunk($input, $size, false) on an Apple M1 using a release build (best of three runs).

input size repetitions base this PR speedup
packed, n=100 10 200,000 144.5 ms 69.7 ms 2.1×
packed, n=1,000 10 20,000 139.9 ms 64.0 ms 2.2×
packed, n=1,000 100 20,000 105.3 ms 38.5 ms 2.7×
packed, n=100,000 100 200 110.7 ms 38.9 ms 2.8×
string keys, n=1,000 10 20,000 140.0 ms 64.1 ms 2.2×
Benchmark script
<?php
function bench(string $label, array $input, int $size, bool $preserve, int $reps): void {
    $best = PHP_FLOAT_MAX;
    for ($run = 0; $run < 3; $run++) {
        $t = hrtime(true);
        for ($r = 0; $r < $reps; $r++) {
            array_chunk($input, $size, $preserve);
        }
        $best = min($best, (hrtime(true) - $t) / 1e6);
    }
    printf("%-28s size=%-5d x%-7d best: %8.1f ms\n", $label, $size, $reps, $best);
}

$hash = [];
for ($i = 0; $i < 1000; $i++) { $hash["k$i"] = $i; }

bench('packed n=100',    range(0, 99),    10,  false, 200000);
bench('packed n=1000',   range(0, 999),   10,  false, 20000);
bench('packed n=1000',   range(0, 999),   100, false, 20000);
bench('packed n=100000', range(0, 99999), 100, false, 200);
bench('string keys n=1000', $hash,        10,  false, 20000);
bench('packed n=1000 preserve', range(0, 999), 10, true, 20000);

When $preserve_keys is not passed, every chunk is a list of at most
$size elements, so each chunk can be built with ZEND_HASH_FILL_PACKED
instead of one zend_hash_next_index_insert() call per element.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant