Skip to content

Commit f59fefc

Browse files
authored
Merge pull request #190 from githubnext/autoloop/tsb-perf-evolve
[Autoloop] [Autoloop: tsb-perf-evolve]
2 parents 2d32f0a + b230a01 commit f59fefc

1 file changed

Lines changed: 66 additions & 4 deletions

File tree

src/core/series.ts

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -712,11 +712,73 @@ export class Series<T extends Scalar = Scalar> {
712712

713713
/** Return a new Series sorted by values. */
714714
sortValues(ascending = true, naPosition: "first" | "last" = "last"): Series<T> {
715-
const pairs = this._values.map((v, i) => ({ v, i }));
716-
pairs.sort((a, b) => compareScalars(a.v, b.v, ascending, naPosition));
715+
const n = this._values.length;
716+
const vals = this._values;
717+
718+
// Pre-partition NaN/null/undefined from finite values in one pass.
719+
// This removes the NaN check from the comparator's hot path.
720+
const finBuf = new Uint32Array(n);
721+
const nanBuf = new Uint32Array(n);
722+
let finCount = 0;
723+
let nanCount = 0;
724+
for (let i = 0; i < n; i++) {
725+
const v = vals[i];
726+
if (v === null || v === undefined || (typeof v === "number" && Number.isNaN(v))) {
727+
nanBuf[nanCount++] = i;
728+
} else {
729+
finBuf[finCount++] = i;
730+
}
731+
}
732+
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.
736+
const finSlice = finBuf.subarray(0, finCount);
737+
if (ascending) {
738+
finSlice.sort((a, b) => {
739+
const av = vals[a] as number | string | boolean;
740+
const bv = vals[b] as number | string | boolean;
741+
return av < bv ? -1 : av > bv ? 1 : 0;
742+
});
743+
} else {
744+
finSlice.sort((a, b) => {
745+
const av = vals[a] as number | string | boolean;
746+
const bv = vals[b] as number | string | boolean;
747+
return av > bv ? -1 : av < bv ? 1 : 0;
748+
});
749+
}
750+
751+
// Build the output permutation and gather values in a single pass.
752+
const perm = new Array<number>(n);
753+
const outData = new Array<T>(n);
754+
let pos = 0;
755+
if (naPosition === "first") {
756+
for (let i = 0; i < nanCount; i++) {
757+
const idx = nanBuf[i]!;
758+
perm[pos] = idx;
759+
outData[pos++] = vals[idx] as T;
760+
}
761+
for (let i = 0; i < finCount; i++) {
762+
const idx = finSlice[i]!;
763+
perm[pos] = idx;
764+
outData[pos++] = vals[idx] as T;
765+
}
766+
} else {
767+
for (let i = 0; i < finCount; i++) {
768+
const idx = finSlice[i]!;
769+
perm[pos] = idx;
770+
outData[pos++] = vals[idx] as T;
771+
}
772+
for (let i = 0; i < nanCount; i++) {
773+
const idx = nanBuf[i]!;
774+
perm[pos] = idx;
775+
outData[pos++] = vals[idx] as T;
776+
}
777+
}
778+
717779
return new Series<T>({
718-
data: pairs.map(({ v }) => v),
719-
index: this.index.take(pairs.map(({ i }) => i)),
780+
data: outData,
781+
index: this.index.take(perm),
720782
dtype: this.dtype,
721783
name: this.name,
722784
});

0 commit comments

Comments
 (0)