Skip to content

Commit b07ee72

Browse files
Iteration 2: Float64Array parallel values for monomorphic numeric sort
Pre-copy finite numeric values into a parallel Float64Array (fvals) during the partition pass. For all-numeric Series the sort comparator now reads fvSlice[a]! - fvSlice[b]! instead of (vals[a] as ...) < (vals[b] as ...), giving the JIT an unboxed, monomorphic Float64Array call site and replacing two branch comparisons with a single FP subtraction per comparison. For string/boolean/mixed Series the existing generic branch comparator is used unchanged. Run: https://github.com/githubnext/tsessebe/actions/runs/24830395075 Co-authored-by: Copilot <[email protected]>
1 parent 5792af4 commit b07ee72

1 file changed

Lines changed: 22 additions & 5 deletions

File tree

src/core/series.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -716,25 +716,42 @@ export class Series<T extends Scalar = Scalar> {
716716
const vals = this._values;
717717

718718
// Pre-partition NaN/null/undefined from finite values in one pass.
719-
// This removes the NaN check from the comparator's hot path.
719+
// fvals mirrors finBuf: fvals[j] holds the numeric value at finBuf[j]
720+
// so the sort comparator reads a typed Float64Array (not a generic T[]),
721+
// giving the JIT a monomorphic, unboxed call site.
720722
const finBuf = new Uint32Array(n);
721723
const nanBuf = new Uint32Array(n);
724+
const fvals = new Float64Array(n);
722725
let finCount = 0;
723726
let nanCount = 0;
727+
let allNumeric = true;
724728
for (let i = 0; i < n; i++) {
725729
const v = vals[i];
726730
if (v === null || v === undefined || (typeof v === "number" && Number.isNaN(v))) {
727731
nanBuf[nanCount++] = i;
728732
} else {
733+
if (typeof v === "number") {
734+
fvals[finCount] = v;
735+
} else {
736+
allNumeric = false;
737+
}
729738
finBuf[finCount++] = i;
730739
}
731740
}
732741

733-
// Sort the finite-index slice in-place using an indirect comparator.
734-
// Dispatching to one of two monomorphic comparators avoids a per-call
735-
// branch on `ascending` inside the sort's hot loop.
742+
// Sort the finite-index slice in-place.
743+
// For all-numeric data use the Float64Array subtraction comparator —
744+
// monomorphic, branchless, and JIT-specialisable.
745+
// For mixed/string data fall back to the generic branch comparator.
736746
const finSlice = finBuf.subarray(0, finCount);
737-
if (ascending) {
747+
const fvSlice = fvals.subarray(0, finCount);
748+
if (allNumeric) {
749+
if (ascending) {
750+
finSlice.sort((a, b) => fvSlice[a]! - fvSlice[b]!);
751+
} else {
752+
finSlice.sort((a, b) => fvSlice[b]! - fvSlice[a]!);
753+
}
754+
} else if (ascending) {
738755
finSlice.sort((a, b) => {
739756
const av = vals[a] as number | string | boolean;
740757
const bv = vals[b] as number | string | boolean;

0 commit comments

Comments
 (0)