Skip to content

Commit c8a0784

Browse files
committed
Optimize sort() and rsort() for packed integer arrays
1 parent f142b81 commit c8a0784

3 files changed

Lines changed: 72 additions & 0 deletions

File tree

Zend/zend_hash.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2990,6 +2990,29 @@ ZEND_API void zend_hash_bucket_packed_swap(Bucket *p, Bucket *q)
29902990
q->h = h;
29912991
}
29922992

2993+
static void zend_hash_packed_zval_swap(void *a, void *b)
2994+
{
2995+
zval tmp = *(zval *) a;
2996+
*(zval *) a = *(zval *) b;
2997+
*(zval *) b = tmp;
2998+
}
2999+
3000+
ZEND_API void ZEND_FASTCALL zend_hash_sort_packed(HashTable *ht, compare_func_t compar)
3001+
{
3002+
IS_CONSISTENT(ht);
3003+
HT_ASSERT_RC1(ht);
3004+
ZEND_ASSERT(HT_IS_PACKED(ht) && HT_IS_WITHOUT_HOLES(ht));
3005+
ZEND_ASSERT(!HT_HAS_ITERATORS(ht));
3006+
3007+
/* Preserve original order for stable comparisons. Swaps must include u2. */
3008+
for (uint32_t i = 0; i < ht->nNumUsed; i++) {
3009+
Z_EXTRA(ht->arPacked[i]) = i;
3010+
}
3011+
zend_sort(ht->arPacked, ht->nNumUsed, sizeof(zval), compar, zend_hash_packed_zval_swap);
3012+
ht->nInternalPointer = 0;
3013+
ht->nNextFreeElement = ht->nNumUsed;
3014+
}
3015+
29933016
static void zend_hash_sort_internal(HashTable *ht, sort_func_t sort, bucket_compare_func_t compar, bool renumber)
29943017
{
29953018
Bucket *p;

Zend/zend_hash.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,10 @@ ZEND_API int zend_hash_compare(HashTable *ht1, const HashTable *ht2, compare_f
304304
ZEND_API void ZEND_FASTCALL zend_hash_sort_ex(HashTable *ht, sort_func_t sort_func, bucket_compare_func_t compare_func, bool renumber);
305305
ZEND_API void ZEND_FASTCALL zend_array_sort_ex(HashTable *ht, sort_func_t sort_func, bucket_compare_func_t compare_func, bool renumber);
306306

307+
/* Sort and renumber a packed array without holes. The comparator must not
308+
* execute user code or modify the array. */
309+
ZEND_API void ZEND_FASTCALL zend_hash_sort_packed(HashTable *ht, compare_func_t compare_func);
310+
307311
static zend_always_inline void ZEND_FASTCALL zend_hash_sort(HashTable *ht, bucket_compare_func_t compare_func, bool renumber) {
308312
zend_hash_sort_ex(ht, zend_sort, compare_func, renumber);
309313
}

ext/standard/array.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,43 @@ PHP_FUNCTION(natcasesort)
695695

696696
typedef bucket_compare_func_t(*get_compare_function)(zend_long);
697697

698+
static int php_array_packed_long_compare(const void *a, const void *b)
699+
{
700+
const zval *lhs = a, *rhs = b;
701+
if (Z_LVAL_P(lhs) != Z_LVAL_P(rhs)) {
702+
return Z_LVAL_P(lhs) > Z_LVAL_P(rhs) ? 1 : -1;
703+
}
704+
return (Z_EXTRA_P(lhs) > Z_EXTRA_P(rhs)) - (Z_EXTRA_P(lhs) < Z_EXTRA_P(rhs));
705+
}
706+
707+
static int php_array_packed_long_reverse_compare(const void *a, const void *b)
708+
{
709+
const zval *lhs = a, *rhs = b;
710+
if (Z_LVAL_P(lhs) != Z_LVAL_P(rhs)) {
711+
return Z_LVAL_P(lhs) < Z_LVAL_P(rhs) ? 1 : -1;
712+
}
713+
return (Z_EXTRA_P(lhs) > Z_EXTRA_P(rhs)) - (Z_EXTRA_P(lhs) < Z_EXTRA_P(rhs));
714+
}
715+
716+
static bool php_array_try_packed_long_sort(HashTable *array, compare_func_t cmp)
717+
{
718+
if (!HT_IS_PACKED(array)
719+
|| !HT_IS_WITHOUT_HOLES(array) || HT_HAS_ITERATORS(array)) {
720+
return false;
721+
}
722+
723+
/* Reject references and other types before changing any element. Integer
724+
* comparisons cannot invoke user code, so the array stays exclusively owned. */
725+
for (uint32_t i = 0; i < array->nNumUsed; i++) {
726+
if (Z_TYPE(array->arPacked[i]) != IS_LONG) {
727+
return false;
728+
}
729+
}
730+
731+
zend_hash_sort_packed(array, cmp);
732+
return true;
733+
}
734+
698735
static zend_always_inline void php_sort(INTERNAL_FUNCTION_PARAMETERS, get_compare_function get_cmp, bool renumber) {
699736
HashTable *array;
700737
zend_long sort_type = PHP_SORT_REGULAR;
@@ -708,6 +745,14 @@ static zend_always_inline void php_sort(INTERNAL_FUNCTION_PARAMETERS, get_compar
708745

709746
cmp = get_cmp(sort_type);
710747

748+
/* Keep small sorts out of the speculative type scan and helper call. */
749+
if (renumber && sort_type == PHP_SORT_REGULAR && array->nNumOfElements >= 64
750+
&& php_array_try_packed_long_sort(array,
751+
get_cmp == php_get_data_compare_func
752+
? php_array_packed_long_compare : php_array_packed_long_reverse_compare)) {
753+
RETURN_TRUE;
754+
}
755+
711756
zend_array_sort(array, cmp, renumber);
712757

713758
RETURN_TRUE;

0 commit comments

Comments
 (0)