@@ -716,25 +716,40 @@ 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 stores numeric values by original row index so the sort comparator
720+ // can read a typed Float64Array (not a generic T[]) at index a/b.
720721 const finBuf = new Uint32Array ( n ) ;
721722 const nanBuf = new Uint32Array ( n ) ;
723+ const fvals = new Float64Array ( n ) ;
722724 let finCount = 0 ;
723725 let nanCount = 0 ;
726+ let allNumeric = true ;
724727 for ( let i = 0 ; i < n ; i ++ ) {
725728 const v = vals [ i ] ;
726729 if ( v === null || v === undefined || ( typeof v === "number" && Number . isNaN ( v ) ) ) {
727730 nanBuf [ nanCount ++ ] = i ;
728731 } else {
732+ if ( typeof v === "number" ) {
733+ fvals [ i ] = v ;
734+ } else {
735+ allNumeric = false ;
736+ }
729737 finBuf [ finCount ++ ] = i ;
730738 }
731739 }
732740
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.
741+ // Sort the finite-index slice in-place.
742+ // For all-numeric data use the Float64Array subtraction comparator —
743+ // monomorphic, branchless, and JIT-specialisable.
744+ // For mixed/string data fall back to the generic branch comparator.
736745 const finSlice = finBuf . subarray ( 0 , finCount ) ;
737- if ( ascending ) {
746+ if ( allNumeric ) {
747+ if ( ascending ) {
748+ finSlice . sort ( ( a , b ) => fvals [ a ] ! - fvals [ b ] ! ) ;
749+ } else {
750+ finSlice . sort ( ( a , b ) => fvals [ b ] ! - fvals [ a ] ! ) ;
751+ }
752+ } else if ( ascending ) {
738753 finSlice . sort ( ( a , b ) => {
739754 const av = vals [ a ] as number | string | boolean ;
740755 const bv = vals [ b ] as number | string | boolean ;
0 commit comments