Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions src/core/series.ts
Original file line number Diff line number Diff line change
Expand Up @@ -716,25 +716,40 @@ export class Series<T extends Scalar = Scalar> {
const vals = this._values;

// Pre-partition NaN/null/undefined from finite values in one pass.
// This removes the NaN check from the comparator's hot path.
// fvals stores numeric values by original row index so the sort comparator
// can read a typed Float64Array (not a generic T[]) at index a/b.
const finBuf = new Uint32Array(n);
const nanBuf = new Uint32Array(n);
const fvals = new Float64Array(n);
let finCount = 0;
let nanCount = 0;
let allNumeric = true;
for (let i = 0; i < n; i++) {
const v = vals[i];
if (v === null || v === undefined || (typeof v === "number" && Number.isNaN(v))) {
nanBuf[nanCount++] = i;
} else {
if (typeof v === "number") {
fvals[i] = v;
} else {
allNumeric = false;
}
finBuf[finCount++] = i;
}
}

// Sort the finite-index slice in-place using an indirect comparator.
// Dispatching to one of two monomorphic comparators avoids a per-call
// branch on `ascending` inside the sort's hot loop.
// Sort the finite-index slice in-place.
// For all-numeric data use the Float64Array subtraction comparator —
// monomorphic, branchless, and JIT-specialisable.
// For mixed/string data fall back to the generic branch comparator.
const finSlice = finBuf.subarray(0, finCount);
if (ascending) {
if (allNumeric) {
if (ascending) {
finSlice.sort((a, b) => fvals[a]! - fvals[b]!);
} else {
finSlice.sort((a, b) => fvals[b]! - fvals[a]!);
}
} else if (ascending) {
finSlice.sort((a, b) => {
const av = vals[a] as number | string | boolean;
const bv = vals[b] as number | string | boolean;
Expand Down
Loading