@@ -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