@@ -23,7 +23,6 @@ module DataFrame.Internal.Aggregation.Kernel.Dense (
2323 denseMaxMinusMin ,
2424) where
2525
26- import Control.Monad (when )
2726import Data.Type.Equality (TestEquality (.. ), type (:~: ) (Refl ))
2827import qualified Data.Vector.Unboxed as VU
2928import qualified Data.Vector.Unboxed.Mutable as VUM
@@ -74,9 +73,7 @@ denseReduce red g nGroups col = case col of
7473{-# INLINEABLE denseReduce #-}
7574
7675{- | The reductions admitted over an Int column. Sum/min/max/mean/count are
77- exact in the Int domain (any merge order gives the same bits); top2sum selects
78- the two largest values (order-independent as a multiset selection) and only
79- adds them once at finalize. Var/std stay with the group-range gather kernel:
76+ exact in the Int domain (any merge order gives the same bits). Var/std stay with the group-range gather kernel:
8077its per-group Welford recurrence replays the interpreter's update order
8178bit-for-bit, which no chunk-merged direct pass can.
8279-}
@@ -87,11 +84,9 @@ denseInt red g nGroups v = case red of
8784 RMin -> Just (fromUnboxedVector (extremaIntDense True g nGroups v))
8885 RMax -> Just (fromUnboxedVector (extremaIntDense False g nGroups v))
8986 RMean -> Just (fromUnboxedVector (meanIntDense g nGroups v))
90- RTop2Sum -> Just (fromUnboxedVector (top2Dense g nGroups v))
91- RTop2Snd -> Just (fromUnboxedVector (top2SndDense g nGroups v))
9287 _ -> Nothing
9388
94- {- | The reductions admitted over a Double column. Count/min/max/top2sum are
89+ {- | The reductions admitted over a Double column. Count/min/max are
9590order-independent (exact per-worker merge, byte-identical at any @-N@). The
9691float sum/mean run sequentially in row order below 'seqFloatGroups' (matching
9792the gather kernel's per-group addition order exactly) and as deterministic
@@ -105,8 +100,6 @@ denseDouble red g nGroups v = case red of
105100 RMean -> Just (fromUnboxedVector (meanDblDense g nGroups v))
106101 RMin -> Just (fromUnboxedVector (extremaDblDense True g nGroups v))
107102 RMax -> Just (fromUnboxedVector (extremaDblDense False g nGroups v))
108- RTop2Sum -> Just (fromUnboxedVector (top2Dense g nGroups v))
109- RTop2Snd -> Just (fromUnboxedVector (top2SndDense g nGroups v))
110103 _ -> Nothing
111104
112105{- | The fused @max a - min b@ direct pass: BOTH extrema accumulate in one
@@ -497,153 +490,6 @@ finalizeMaxMinusMin nGroups mx mn = do
497490 go 0
498491 VU. unsafeFreeze out
499492
500- -------------------------------------------------------------------------------
501- -- Top-2 sum (order-independent multiset selection; one float add at finalize)
502- -------------------------------------------------------------------------------
503-
504- {- | Sum of the two largest values per group. Each accumulator holds the
505- (largest, second-largest) pair seen so far; merging two pairs keeps the top two
506- of the four candidates. No float ADDITION happens until the single @m1 + m2@ at
507- finalize, so the result is byte-identical to the gather kernel regardless of
508- chunking. Mirrors the gather kernel exactly, including the @realToFrac@ per
509- element and the @-inf -> 0@ guards at finalize.
510- -}
511- top2Dense ::
512- (VU. Unbox a , Real a ) => VU. Vector Int -> Int -> VU. Vector a -> VU. Vector Double
513- {-# SPECIALIZE top2Dense ::
514- VU.Vector Int -> Int -> VU.Vector Int -> VU.Vector Double
515- #-}
516- {-# SPECIALIZE top2Dense ::
517- VU.Vector Int -> Int -> VU.Vector Double -> VU.Vector Double
518- #-}
519- top2Dense g nGroups v
520- | not (shouldPar n) = unsafePerformIO $ do
521- (m1, m2) <- top2Chunk g v nGroups 0 n
522- finalizeTop2 nGroups m1 m2
523- | otherwise = unsafePerformIO $ do
524- parts <- runPartialsOver n capabilities (top2Chunk g v nGroups)
525- (m1, m2) <- mergeTop2 nGroups parts
526- finalizeTop2 nGroups m1 m2
527- where
528- ! n = VU. length v
529- {- INLINEABLE (not NOINLINE) so the SPECIALIZE pragmas above take effect; pure
530- function of its arguments, so unsafePerformIO sharing is not a concern. -}
531- {-# INLINEABLE top2Dense #-}
532-
533- {- | Second-largest value per group: the exact same per-worker
534- (largest, second-largest) accumulator and merge as 'top2Dense'
535- ('top2Chunk'/'mergeTop2'), finalized to the second max alone. A group of
536- size < 2 finalizes its @-inf@ seed to NaN — documented behaviour (the
537- db-benchmark Q8 data has no size-1 @id6@ groups). Order-independent multiset
538- selection, so byte-identical at any @-N@.
539- -}
540- top2SndDense ::
541- (VU. Unbox a , Real a ) => VU. Vector Int -> Int -> VU. Vector a -> VU. Vector Double
542- {-# SPECIALIZE top2SndDense ::
543- VU.Vector Int -> Int -> VU.Vector Int -> VU.Vector Double
544- #-}
545- {-# SPECIALIZE top2SndDense ::
546- VU.Vector Int -> Int -> VU.Vector Double -> VU.Vector Double
547- #-}
548- top2SndDense g nGroups v
549- | not (shouldPar n) = unsafePerformIO $ do
550- (m1, m2) <- top2Chunk g v nGroups 0 n
551- finalizeTop2Snd nGroups m1 m2
552- | otherwise = unsafePerformIO $ do
553- parts <- runPartialsOver n capabilities (top2Chunk g v nGroups)
554- (m1, m2) <- mergeTop2 nGroups parts
555- finalizeTop2Snd nGroups m1 m2
556- where
557- ! n = VU. length v
558- {- INLINEABLE (not NOINLINE) so the SPECIALIZE pragmas above take effect; pure
559- function of its arguments, so unsafePerformIO sharing is not a concern. -}
560- {-# INLINEABLE top2SndDense #-}
561-
562- top2Chunk ::
563- (VU. Unbox a , Real a ) =>
564- VU. Vector Int ->
565- VU. Vector a ->
566- Int ->
567- Int ->
568- Int ->
569- IO (VUM. IOVector Double , VUM. IOVector Double )
570- top2Chunk g v nGroups lo hi = do
571- let ninf = negate (1 / 0 ) :: Double
572- m1 <- VUM. replicate nGroups ninf
573- m2 <- VUM. replicate nGroups ninf
574- let go ! i
575- | i >= hi = pure ()
576- | otherwise = do
577- let ! k = VU. unsafeIndex g i
578- ! x = realToFrac (VU. unsafeIndex v i)
579- a1 <- VUM. unsafeRead m1 k
580- if x > a1
581- then do
582- VUM. unsafeWrite m1 k x
583- VUM. unsafeWrite m2 k a1
584- else do
585- a2 <- VUM. unsafeRead m2 k
586- when (x > a2) (VUM. unsafeWrite m2 k x)
587- go (i + 1 )
588- go lo
589- pure (m1, m2)
590-
591- {- | Top two of the four candidates @{a1, a2, b1, b2}@ per group (each pair
592- already ordered @m1 >= m2@, @-inf@ seeds included).
593- -}
594- mergeTop2 ::
595- Int ->
596- [(VUM. IOVector Double , VUM. IOVector Double )] ->
597- IO (VUM. IOVector Double , VUM. IOVector Double )
598- mergeTop2 nGroups parts = case parts of
599- [] -> error " mergeTop2: no partials"
600- ((m10, m20) : rest) -> do
601- let add (m1, m2) = do
602- let go ! k
603- | k >= nGroups = pure ()
604- | otherwise = do
605- a1 <- VUM. unsafeRead m10 k
606- a2 <- VUM. unsafeRead m20 k
607- b1 <- VUM. unsafeRead m1 k
608- b2 <- VUM. unsafeRead m2 k
609- if b1 > a1
610- then do
611- VUM. unsafeWrite m10 k b1
612- VUM. unsafeWrite m20 k (max a1 b2)
613- else VUM. unsafeWrite m20 k (max a2 b1)
614- go (k + 1 )
615- go 0
616- mapM_ add rest
617- pure (m10, m20)
618-
619- finalizeTop2 ::
620- Int -> VUM. IOVector Double -> VUM. IOVector Double -> IO (VU. Vector Double )
621- finalizeTop2 nGroups m1 m2 = do
622- out <- VUM. new nGroups
623- let go ! k
624- | k >= nGroups = pure ()
625- | otherwise = do
626- a1 <- VUM. unsafeRead m1 k
627- a2 <- VUM. unsafeRead m2 k
628- let s = (if isInfinite a1 then 0 else a1) + (if isInfinite a2 then 0 else a2)
629- VUM. unsafeWrite out k s
630- go (k + 1 )
631- go 0
632- VU. unsafeFreeze out
633-
634- finalizeTop2Snd ::
635- Int -> VUM. IOVector Double -> VUM. IOVector Double -> IO (VU. Vector Double )
636- finalizeTop2Snd nGroups _m1 m2 = do
637- out <- VUM. new nGroups
638- let go ! k
639- | k >= nGroups = pure ()
640- | otherwise = do
641- a2 <- VUM. unsafeRead m2 k
642- VUM. unsafeWrite out k (if isInfinite a2 then 0 / 0 else a2)
643- go (k + 1 )
644- go 0
645- VU. unsafeFreeze out
646-
647493-------------------------------------------------------------------------------
648494-- Integer mean (exact integer sum + count, divided once -> order-independent)
649495-------------------------------------------------------------------------------
0 commit comments