Skip to content

ext/standard: Optimize sort() and rsort() for packed arrays - #23585

Draft
LamentXU123 wants to merge 2 commits into
php:masterfrom
LamentXU123:opt-sort
Draft

ext/standard: Optimize sort() and rsort() for packed arrays#23585
LamentXU123 wants to merge 2 commits into
php:masterfrom
LamentXU123:opt-sort

Conversation

@LamentXU123

@LamentXU123 LamentXU123 commented Sep 5, 2026

Copy link
Copy Markdown
Member

After the last trivial one-element optimization I get to be familiar with how sort works in Zend. So, now, except the array is empty or only has one element, we convert arrays to mixed storage before sorting, and convert them back after we sort them.

The reason we are doing this is because the internal sorting functions is using Buckets, so we can preserve the keys when sorting the values. Packed arrays (most cases we are sorting them instead of unpacked and mixed arrays), are sequence zvals, and don't have customized keys.

This let me thinking. Can we just implement a sort internally which completely works with zvals and not Buckets? So we don't need to convert them to Bucket and convert them back again when sorting packed arrays that don't have external keys at all?

I have a idea to implement a sort works with integer arrays entirely depending on zvals. There it is. I set a threshold to use this new internal function instead of the original one when the array to be sorted is large (>= 64 elements), packed and contains fully int. This is because sort functions with fully int arrays is easy to implement (I don't need to consider user's fallback function etc.) and since we are now not doing creating new Buckets and destroying them the benefits mainly comes from larger arrays, both in the aspect of time and memory spaces.

I ran benchmarks on a 64-elements, fully int array. Also, somehow this is also way more faster when we are sorting on a already sorted array, which is like a bonus.

Benchmark:

Case Baseline batch Candidate batch Time change
Random integers, sort() 178.99 ms 149.92 ms -16.24%
Random integers, rsort() 235.23 ms 127.49 ms -45.80%
Already sorted integers, sort() 66.86 ms 30.15 ms -54.91%

And this is the results with a 1,000,000 element array FYI.

Case Baseline Candidate Time change
Random integers, sort() 355.47 ms 226.61 ms -36.25%
Random integers, rsort() 542.42 ms 187.24 ms -65.48%
Already sorted integers, sort() 162.24 ms 58.58 ms -63.89%

@LamentXU123 LamentXU123 changed the title ext/standard: Optimize sort() and rsort() for large packed integer arrays ext/standard: Optimize sort() and rsort() for packed integer arrays Sep 6, 2026
@staabm

staabm commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Nice to see faster sorting. In PHPStan profiles we see slow sorting in https://github.com/phpstan/phpstan-src/blob/2.3.x/src/Type/TypeCombinator.php in most profiles (which is not sorting on pure integers though)

@LamentXU123 LamentXU123 changed the title ext/standard: Optimize sort() and rsort() for packed integer arrays ext/standard: Optimize sort() and rsort() for large packed integer arrays Sep 6, 2026
@LamentXU123
LamentXU123 marked this pull request as draft September 6, 2026 16:32
@LamentXU123

Copy link
Copy Markdown
Member Author

TBH, I don't think this is a completed PR. The idea is good, but we can't just implement this only for integers. Ideally, we can implement a sort completely supporting any zvals to be used by packed arrays.

I will be working on that idea in the coming days. So this is now drafted, sorry to ping you so early. I will request reviews when I think the implementaion is done.

Feel free to comment about the idea itself :)

@LamentXU123 LamentXU123 changed the title ext/standard: Optimize sort() and rsort() for large packed integer arrays ext/standard: Optimize sort() and rsort() for packed arrays Sep 6, 2026
Sort packed inputs to sort(), rsort(), and usort() directly as zvals,
without a size cutoff. Share value comparison implementations and flag
dispatch with Bucket wrappers, retaining stable ties and callback lifetime
protection. Keep the metadata-free integer specialization private.

Skip the lifetime pin and possible GC root registration for built-in sorts
of direct scalars and strings. Continue scanning after any integer prefix;
exclude NAN because string coercion warnings can invoke error handlers.

Compact holes in both packed APIs and relocate active iterators as in
rehash, including past-the-end cursors. Initialize original positions in
both public APIs and preserve empty-array metadata. Share the array sort
release logic with the existing Bucket path.

Document numeric-index visibility during implicit comparison callbacks.
Cover comparison modes, pivot boundaries, stable ties, references, holes,
iterator relocation, append metadata, exceptions, and reentrancy through
object conversions and NAN coercion warnings.
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.

2 participants