Skip to content

Commit eb74a1f

Browse files
authored
Merge pull request #272 from githubnext/autoloop/tsb-perf-evolve
[Autoloop: tsb-perf-evolve]
2 parents d566820 + b0200de commit eb74a1f

1 file changed

Lines changed: 43 additions & 40 deletions

File tree

src/core/series.ts

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -764,8 +764,12 @@ export class Series<T extends Scalar = Scalar> {
764764
let nanCount = 0;
765765
let allNumeric = true;
766766

767-
// Single pass: partition NaN/null and initialise AoS radix entries for finite numerics.
768-
// fvals is indexed by compact slot (finCount) so reads and writes are sequential.
767+
// Clear histograms before the init loop so we can accumulate them inline.
768+
_rxHisto.fill(0);
769+
770+
// Single pass: partition NaN/null, initialise AoS radix entries for finite
771+
// numerics, and accumulate all 8 histograms simultaneously — eliminating the
772+
// separate O(n) histogram scan that the previous implementation required.
769773
for (let i = 0; i < n; i++) {
770774
const v = vals[i];
771775
if (v === null || v === undefined || (typeof v === "number" && Number.isNaN(v))) {
@@ -791,6 +795,24 @@ export class Series<T extends Scalar = Scalar> {
791795
_rxA[base] = i;
792796
_rxA[base + 1] = lo;
793797
_rxA[base + 2] = hi;
798+
// Accumulate all 8 histogram passes inline — no second scan needed.
799+
let idx: number;
800+
idx = lo & 0xff;
801+
_rxHisto[idx] = _rxHisto[idx]! + 1;
802+
idx = 256 + ((lo >>> 8) & 0xff);
803+
_rxHisto[idx] = _rxHisto[idx]! + 1;
804+
idx = 512 + ((lo >>> 16) & 0xff);
805+
_rxHisto[idx] = _rxHisto[idx]! + 1;
806+
idx = 768 + ((lo >>> 24) & 0xff);
807+
_rxHisto[idx] = _rxHisto[idx]! + 1;
808+
idx = 1024 + (hi & 0xff);
809+
_rxHisto[idx] = _rxHisto[idx]! + 1;
810+
idx = 1280 + ((hi >>> 8) & 0xff);
811+
_rxHisto[idx] = _rxHisto[idx]! + 1;
812+
idx = 1536 + ((hi >>> 16) & 0xff);
813+
_rxHisto[idx] = _rxHisto[idx]! + 1;
814+
idx = 1792 + ((hi >>> 24) & 0xff);
815+
_rxHisto[idx] = _rxHisto[idx]! + 1;
794816
} else {
795817
allNumeric = false;
796818
}
@@ -807,35 +829,9 @@ export class Series<T extends Scalar = Scalar> {
807829

808830
if (allNumeric && finCount > 0) {
809831
// ── LSD radix sort: 8 passes × 8 bits over IEEE-754 transformed keys ──
810-
// _rxA is already initialised by the merged loop above.
832+
// _rxA and _rxHisto are already initialised by the merged loop above.
811833
// AoS layout: srcBuf[i*3]=origIdx, srcBuf[i*3+1]=loKey, srcBuf[i*3+2]=hiKey.
812834

813-
// Pre-compute all 8 histograms in a single O(n) scan, saving 7 redundant
814-
// count loops. _rxHisto[pass*256 + byte] = count of elements with that byte.
815-
_rxHisto.fill(0);
816-
for (let i = 0; i < finCount; i++) {
817-
const si = i * 3;
818-
const lo = srcBuf[si + 1]!;
819-
const hi = srcBuf[si + 2]!;
820-
let idx: number;
821-
idx = 0 * 256 + (lo & 0xff);
822-
_rxHisto[idx] = _rxHisto[idx]! + 1;
823-
idx = 1 * 256 + ((lo >>> 8) & 0xff);
824-
_rxHisto[idx] = _rxHisto[idx]! + 1;
825-
idx = 2 * 256 + ((lo >>> 16) & 0xff);
826-
_rxHisto[idx] = _rxHisto[idx]! + 1;
827-
idx = 3 * 256 + ((lo >>> 24) & 0xff);
828-
_rxHisto[idx] = _rxHisto[idx]! + 1;
829-
idx = 4 * 256 + (hi & 0xff);
830-
_rxHisto[idx] = _rxHisto[idx]! + 1;
831-
idx = 5 * 256 + ((hi >>> 8) & 0xff);
832-
_rxHisto[idx] = _rxHisto[idx]! + 1;
833-
idx = 6 * 256 + ((hi >>> 16) & 0xff);
834-
_rxHisto[idx] = _rxHisto[idx]! + 1;
835-
idx = 7 * 256 + ((hi >>> 24) & 0xff);
836-
_rxHisto[idx] = _rxHisto[idx]! + 1;
837-
}
838-
839835
// Convert each histogram to an exclusive prefix sum (cumulative offsets).
840836
for (let pass = 0; pass < 8; pass++) {
841837
const base = pass * 256;
@@ -855,8 +851,8 @@ export class Series<T extends Scalar = Scalar> {
855851
const keyOff = pass < 4 ? 1 : 2;
856852
const shift = (pass % 4) * 8;
857853
const histoBase = pass * 256;
858-
for (let i = 0; i < finCount; i++) {
859-
const si = i * 3;
854+
// Use accumulated stride counter (si += 3) to avoid i*3 multiply per element.
855+
for (let i = 0, si = 0; i < finCount; i++, si += 3) {
860856
const bucket = (srcBuf[si + keyOff]! >>> shift) & 0xff;
861857
const p = _rxHisto[histoBase + bucket]!;
862858
_rxHisto[histoBase + bucket] = p + 1;
@@ -904,15 +900,15 @@ export class Series<T extends Scalar = Scalar> {
904900
}
905901
if (allNumeric) {
906902
if (ascending) {
907-
for (let i = 0; i < finCount; i++) {
908-
const idx = srcBuf[i * 3]!;
903+
for (let i = 0, si = 0; i < finCount; i++, si += 3) {
904+
const idx = srcBuf[si]!;
909905
perm[pos] = idx;
910906
outData[pos] = vals[idx] as T;
911907
pos = pos + 1;
912908
}
913909
} else {
914-
for (let i = finCount - 1; i >= 0; i--) {
915-
const idx = srcBuf[i * 3]!;
910+
for (let i = finCount - 1, si = (finCount - 1) * 3; i >= 0; i--, si -= 3) {
911+
const idx = srcBuf[si]!;
916912
perm[pos] = idx;
917913
outData[pos] = vals[idx] as T;
918914
pos = pos + 1;
@@ -929,15 +925,15 @@ export class Series<T extends Scalar = Scalar> {
929925
} else {
930926
if (allNumeric) {
931927
if (ascending) {
932-
for (let i = 0; i < finCount; i++) {
933-
const idx = srcBuf[i * 3]!;
928+
for (let i = 0, si = 0; i < finCount; i++, si += 3) {
929+
const idx = srcBuf[si]!;
934930
perm[pos] = idx;
935931
outData[pos] = vals[idx] as T;
936932
pos = pos + 1;
937933
}
938934
} else {
939-
for (let i = finCount - 1; i >= 0; i--) {
940-
const idx = srcBuf[i * 3]!;
935+
for (let i = finCount - 1, si = (finCount - 1) * 3; i >= 0; i--, si -= 3) {
936+
const idx = srcBuf[si]!;
941937
perm[pos] = idx;
942938
outData[pos] = vals[idx] as T;
943939
pos = pos + 1;
@@ -959,9 +955,16 @@ export class Series<T extends Scalar = Scalar> {
959955
}
960956
}
961957

958+
// RangeIndex fast path: for a default 0-based RangeIndex the output index is
959+
// just perm itself — skip 100k bounds-checked at() calls from index.take().
960+
const outIndex: Index<Label> =
961+
this.index instanceof RangeIndex && this.index.start === 0 && this.index.step === 1
962+
? new Index<Label>(perm, this.index.name)
963+
: this.index.take(perm);
964+
962965
return new Series<T>({
963966
data: outData,
964-
index: this.index.take(perm),
967+
index: outIndex,
965968
dtype: this.dtype,
966969
name: this.name,
967970
});

0 commit comments

Comments
 (0)