array.c: add integer-specialized fast path for Array#sort!

when all elements are integers and no block is given, use
specialized heapify/insertion_sort that compare mrb_int values
directly, bypassing sort_cmp entirely. this eliminates per-comparison
overhead of GC arena save/restore, type checking, and array
modification checks.

the pre-scan to detect all-integer arrays is O(n), negligible
compared to O(n log n) sort. non-integer and block sorts are
unaffected.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-03-20 16:48:47 +09:00
parent 02bb943960
commit 5364c4167e
+93 -1
View File
@@ -2081,6 +2081,79 @@ mrb_ary_delete(mrb_state *mrb, mrb_value self)
#define SMALL_ARRAY_SORT_THRESHOLD 16
/* Check if all elements in the array are integers (fast path candidate) */
static mrb_bool
ary_all_fixnum_p(const mrb_value *a, mrb_int n)
{
for (mrb_int i = 0; i < n; i++) {
if (!mrb_integer_p(a[i])) return FALSE;
}
return TRUE;
}
/* Integer-specialized heapify: no sort_cmp overhead, direct comparison */
static void
heapify_fixnum(mrb_value *a, mrb_int index, mrb_int size)
{
mrb_int val = mrb_integer(a[index]);
while (1) {
mrb_int child = 2 * index + 1;
if (child >= size) break;
if (child + 1 < size && mrb_integer(a[child + 1]) > mrb_integer(a[child])) {
child++;
}
if (mrb_integer(a[child]) <= val) break;
a[index] = a[child];
index = child;
}
SET_FIXNUM_VALUE(a[index], val);
}
/* Integer-specialized Floyd's bottom-up heap deletion */
static void
heap_delete_root_fixnum(mrb_value *a, mrb_int size)
{
mrb_int last = mrb_integer(a[0]);
mrb_int hole = 0;
mrb_int child = 1;
while (child + 1 < size) {
if (mrb_integer(a[child + 1]) > mrb_integer(a[child])) {
child++;
}
a[hole] = a[child];
hole = child;
child = 2 * hole + 1;
}
if (child < size) {
a[hole] = a[child];
hole = child;
}
while (hole > 0) {
mrb_int parent = (hole - 1) / 2;
if (mrb_integer(a[parent]) >= last) break;
a[hole] = a[parent];
hole = parent;
}
SET_FIXNUM_VALUE(a[hole], last);
}
/* Integer-specialized insertion sort */
static void
insertion_sort_fixnum(mrb_value *a, mrb_int size)
{
for (mrb_int i = 1; i < size; i++) {
mrb_int key = mrb_integer(a[i]);
mrb_int j = i - 1;
while (j >= 0 && mrb_integer(a[j]) > key) {
a[j + 1] = a[j];
j--;
}
SET_FIXNUM_VALUE(a[j + 1], key);
}
}
static mrb_bool
sort_cmp(mrb_state *mrb, mrb_value ary, mrb_value a_val, mrb_value b_val, mrb_value blk)
@@ -2243,7 +2316,26 @@ mrb_ary_sort_bang(mrb_state *mrb, mrb_value ary)
mrb_value *a = RARRAY_PTR(ary);
/* Algorithm selection based on array size */
/* Integer fast path: no block and all elements are integers */
if (mrb_nil_p(blk) && ary_all_fixnum_p(a, n)) {
if (n <= SMALL_ARRAY_SORT_THRESHOLD) {
insertion_sort_fixnum(a, n);
}
else {
for (mrb_int i = n / 2 - 1; i >= 0; i--) {
heapify_fixnum(a, i, n);
}
for (mrb_int i = n - 1; i > 0; i--) {
mrb_value tmp = a[0];
a[0] = a[i];
a[i] = tmp;
heap_delete_root_fixnum(a, i);
}
}
return ary;
}
/* General path */
if (n <= SMALL_ARRAY_SORT_THRESHOLD) {
/* Use insertion sort for small arrays */
insertion_sort(mrb, ary, a, n, blk);