dataframe-core-2.0.0.0: src-internal/DataFrame/Internal/AggKernelPar.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ExplicitNamespaces #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
-- | Parallel scatter-accumulate aggregation kernel.
module DataFrame.Internal.AggKernelPar (
scatterReducePar,
momentScatterPar,
) where
import Control.Concurrent (forkIO, getNumCapabilities)
import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)
import Control.Exception (SomeException, throwIO, try)
import Control.Monad (when)
import Data.Type.Equality (TestEquality (..), type (:~:) (Refl))
import qualified Data.Vector.Unboxed as VU
import qualified Data.Vector.Unboxed.Mutable as VUM
import System.IO.Unsafe (unsafePerformIO)
import Type.Reflection (typeRep)
import DataFrame.Internal.AggKernel (
Reduction (..),
scatterColumnToDouble,
scatterReduce,
)
import DataFrame.Internal.AggPlan (Moments (..), momentScatter)
import DataFrame.Internal.Column (
Column (..),
Columnable,
fromUnboxedVector,
materializePacked,
)
parThreshold :: Int
parThreshold = 200000
capabilities :: Int
capabilities = unsafePerformIO getNumCapabilities
{-# NOINLINE capabilities #-}
-- | Whether to take the parallel path at this row count.
shouldPar :: Int -> Bool
shouldPar n = n >= parThreshold && capabilities > 1
groupRangeBounds :: VU.Vector Int -> Int -> Int -> VU.Vector Int
groupRangeBounds offs nGroups caps = VU.create $ do
b <- VUM.new (caps + 1)
let !nRows = VU.unsafeIndex offs nGroups
!per = max 1 ((nRows + caps - 1) `div` caps)
adv !target !gg
| gg >= nGroups = nGroups
| VU.unsafeIndex offs gg >= target = gg
| otherwise = adv target (gg + 1)
go !w !prev
| w >= caps = VUM.unsafeWrite b caps nGroups
| otherwise = do
let !target = min nRows (w * per)
!g = adv target prev
VUM.unsafeWrite b w g
go (w + 1) g
VUM.unsafeWrite b 0 0
go 1 0
pure b
forEachRange :: VU.Vector Int -> Int -> (Int -> Int -> IO ()) -> IO ()
forEachRange bounds caps act
| caps <= 1 = act (VU.unsafeIndex bounds 0) (VU.unsafeIndex bounds caps)
| otherwise = do
vars <- mapM spawn [0 .. caps - 1]
results <- mapM takeMVar vars
mapM_ (either (throwIO :: SomeException -> IO ()) pure) results
where
spawn w = do
var <- newEmptyMVar
let !s = VU.unsafeIndex bounds w
!e = VU.unsafeIndex bounds (w + 1)
_ <- forkIO (try (act s e) >>= putMVar var)
pure var
scatterReducePar ::
Reduction -> VU.Vector Int -> VU.Vector Int -> Int -> Column -> Maybe Column
scatterReducePar red vis offs nGroups col
| not (shouldPar (VU.length vis)) || nGroups <= 1 =
scatterReduce red (rtgFromVis vis offs nGroups) nGroups col
| otherwise = case col of
UnboxedColumn Nothing (v :: VU.Vector a) ->
case testEquality (typeRep @a) (typeRep @Int) of
Just Refl -> Just (reduceParTyped red vis offs nGroups v intIdent)
Nothing -> case testEquality (typeRep @a) (typeRep @Double) of
Just Refl -> Just (reduceParTyped red vis offs nGroups v dblIdent)
Nothing -> Nothing
p@(PackedText _ _) -> scatterReducePar red vis offs nGroups (materializePacked p)
_ -> Nothing
{-# NOINLINE scatterReducePar #-}
rtgFromVis :: VU.Vector Int -> VU.Vector Int -> Int -> VU.Vector Int
rtgFromVis vis offs nGroups = VU.create $ do
let n = VU.length vis
rtg <- VUM.new (max 1 n)
let go !g
| g >= nGroups = pure ()
| otherwise = do
let !e = VU.unsafeIndex offs (g + 1)
inner !pos
| pos >= e = pure ()
| otherwise = do
VUM.unsafeWrite rtg (VU.unsafeIndex vis pos) g
inner (pos + 1)
inner (VU.unsafeIndex offs g)
go (g + 1)
go 0
pure rtg
data Idents a = Idents {minSeed :: !a, maxSeed :: !a}
intIdent :: Idents Int
intIdent = Idents maxBound minBound
dblIdent :: Idents Double
dblIdent = Idents (1 / 0) (negate (1 / 0))
reduceParTyped ::
forall a.
(Columnable a, VU.Unbox a, Num a, Ord a, Real a) =>
Reduction ->
VU.Vector Int ->
VU.Vector Int ->
Int ->
VU.Vector a ->
Idents a ->
Column
reduceParTyped red vis offs nGroups v idents =
let !caps = capabilities
!bounds = groupRangeBounds offs nGroups caps
in case red of
RCount -> fromUnboxedVector (unsafePerformIO (countPar vis offs nGroups caps bounds))
RSum -> fromUnboxedVector (unsafePerformIO (sumPar vis offs nGroups v caps bounds))
RMin ->
fromUnboxedVector
(unsafePerformIO (extremaPar min (minSeed idents) vis offs nGroups v caps bounds))
RMax ->
fromUnboxedVector
(unsafePerformIO (extremaPar max (maxSeed idents) vis offs nGroups v caps bounds))
RMean -> fromUnboxedVector (unsafePerformIO (meanPar vis offs nGroups v caps bounds))
RVar ->
fromUnboxedVector
(unsafePerformIO (varPar False vis offs nGroups v caps bounds))
RStd ->
fromUnboxedVector (unsafePerformIO (varPar True vis offs nGroups v caps bounds))
RTop2Sum -> fromUnboxedVector (unsafePerformIO (top2Par vis offs nGroups v caps bounds))
{-# INLINE reduceParTyped #-}
-- | Iterate the rows of groups @[gs, ge)@ in @valueIndices@/group order.
overGroups ::
VU.Vector Int -> VU.Vector Int -> Int -> Int -> (Int -> Int -> IO ()) -> IO ()
overGroups vis offs gs ge step = grp gs
where
grp !g
| g >= ge = pure ()
| otherwise = do
let !e = VU.unsafeIndex offs (g + 1)
inner !pos
| pos >= e = pure ()
| otherwise = step g (VU.unsafeIndex vis pos) >> inner (pos + 1)
inner (VU.unsafeIndex offs g)
grp (g + 1)
{-# INLINE overGroups #-}
countPar ::
VU.Vector Int ->
VU.Vector Int ->
Int ->
Int ->
VU.Vector Int ->
IO (VU.Vector Int)
countPar _vis offs nGroups caps bounds = do
out <- VUM.replicate nGroups (0 :: Int)
forEachRange bounds caps $ \gs ge ->
let grp !g
| g >= ge = pure ()
| otherwise = do
let !c = VU.unsafeIndex offs (g + 1) - VU.unsafeIndex offs g
VUM.unsafeWrite out g c
grp (g + 1)
in grp gs
VU.unsafeFreeze out
sumPar ::
(VU.Unbox a, Num a) =>
VU.Vector Int ->
VU.Vector Int ->
Int ->
VU.Vector a ->
Int ->
VU.Vector Int ->
IO (VU.Vector a)
sumPar vis offs nGroups v caps bounds = do
out <- VUM.replicate nGroups 0
forEachRange bounds caps $ \gs ge ->
overGroups vis offs gs ge $ \g row -> do
cur <- VUM.unsafeRead out g
VUM.unsafeWrite out g (cur + VU.unsafeIndex v row)
VU.unsafeFreeze out
{-# INLINE sumPar #-}
extremaPar ::
(VU.Unbox a) =>
(a -> a -> a) ->
a ->
VU.Vector Int ->
VU.Vector Int ->
Int ->
VU.Vector a ->
Int ->
VU.Vector Int ->
IO (VU.Vector a)
extremaPar combine seed vis offs nGroups v caps bounds = do
out <- VUM.replicate nGroups seed
forEachRange bounds caps $ \gs ge ->
overGroups vis offs gs ge $ \g row -> do
cur <- VUM.unsafeRead out g
VUM.unsafeWrite out g (combine cur (VU.unsafeIndex v row))
VU.unsafeFreeze out
{-# INLINE extremaPar #-}
meanPar ::
(VU.Unbox a, Real a) =>
VU.Vector Int ->
VU.Vector Int ->
Int ->
VU.Vector a ->
Int ->
VU.Vector Int ->
IO (VU.Vector Double)
meanPar vis offs nGroups v caps bounds = do
s <- VUM.replicate nGroups (0 :: Double)
cnt <- VUM.replicate nGroups (0 :: Int)
forEachRange bounds caps $ \gs ge ->
overGroups vis offs gs ge $ \g row -> do
let !x = realToFrac (VU.unsafeIndex v row)
cs <- VUM.unsafeRead s g
VUM.unsafeWrite s g (cs + x)
cc <- VUM.unsafeRead cnt g
VUM.unsafeWrite cnt g (cc + 1)
out <- VUM.new nGroups
let fin !k
| k >= nGroups = pure ()
| otherwise = do
sv <- VUM.unsafeRead s k
c <- VUM.unsafeRead cnt k
VUM.unsafeWrite out k (if c == 0 then 0 / 0 else sv / fromIntegral c)
fin (k + 1)
fin 0
VU.unsafeFreeze out
{-# INLINE meanPar #-}
varPar ::
(VU.Unbox a, Real a) =>
Bool ->
VU.Vector Int ->
VU.Vector Int ->
Int ->
VU.Vector a ->
Int ->
VU.Vector Int ->
IO (VU.Vector Double)
varPar takeSqrt vis offs nGroups v caps bounds = do
cnt <- VUM.replicate nGroups (0 :: Int)
meanV <- VUM.replicate nGroups (0 :: Double)
m2 <- VUM.replicate nGroups (0 :: Double)
forEachRange bounds caps $ \gs ge ->
overGroups vis offs gs ge $ \g row -> do
let !x = realToFrac (VU.unsafeIndex v row)
c <- VUM.unsafeRead cnt g
mu <- VUM.unsafeRead meanV g
mm <- VUM.unsafeRead m2 g
let !c' = c + 1
!delta = x - mu
!mu' = mu + delta / fromIntegral c'
!mm' = mm + delta * (x - mu')
VUM.unsafeWrite cnt g c'
VUM.unsafeWrite meanV g mu'
VUM.unsafeWrite m2 g mm'
out <- VUM.new nGroups
let fin !k
| k >= nGroups = pure ()
| otherwise = do
c <- VUM.unsafeRead cnt k
mm <- VUM.unsafeRead m2 k
let var = if c < 2 then 0 else mm / fromIntegral (c - 1)
VUM.unsafeWrite out k (if takeSqrt then sqrt var else var)
fin (k + 1)
fin 0
VU.unsafeFreeze out
{-# INLINE varPar #-}
top2Par ::
(VU.Unbox a, Real a) =>
VU.Vector Int ->
VU.Vector Int ->
Int ->
VU.Vector a ->
Int ->
VU.Vector Int ->
IO (VU.Vector Double)
top2Par vis offs nGroups v caps bounds = do
let ninf = negate (1 / 0) :: Double
m1 <- VUM.replicate nGroups ninf
m2 <- VUM.replicate nGroups ninf
forEachRange bounds caps $ \gs ge ->
overGroups vis offs gs ge $ \g row -> do
let !x = realToFrac (VU.unsafeIndex v row)
a1 <- VUM.unsafeRead m1 g
if x > a1
then do
VUM.unsafeWrite m1 g x
VUM.unsafeWrite m2 g a1
else do
a2 <- VUM.unsafeRead m2 g
when (x > a2) (VUM.unsafeWrite m2 g x)
out <- VUM.new nGroups
let fin !k
| k >= nGroups = pure ()
| otherwise = do
a1 <- VUM.unsafeRead m1 k
a2 <- VUM.unsafeRead m2 k
let sm = (if isInfinite a1 then 0 else a1) + (if isInfinite a2 then 0 else a2)
VUM.unsafeWrite out k sm
fin (k + 1)
fin 0
VU.unsafeFreeze out
{-# INLINE top2Par #-}
-------------------------------------------------------------------------------
-- Parallel fused two-column moments (Q9)
-------------------------------------------------------------------------------
{- | Parallel counterpart of 'momentScatter': one fused pass over both columns,
each group's six sums accumulated within one worker's range. Byte-identical to
'momentScatter'. 'Nothing' unless both columns are non-null unboxed Int/Double.
-}
momentScatterPar ::
VU.Vector Int -> VU.Vector Int -> Int -> Column -> Column -> Maybe Moments
momentScatterPar vis offs nGroups colX colY
| not (shouldPar (VU.length vis)) || nGroups <= 1 =
momentScatter (rtgFromVis vis offs nGroups) nGroups colX colY
| otherwise = do
xs <- scatterColumnToDouble colX
ys <- scatterColumnToDouble colY
let !caps = capabilities
!bounds = groupRangeBounds offs nGroups caps
pure (unsafePerformIO (momentPar vis offs nGroups xs ys caps bounds))
{-# NOINLINE momentScatterPar #-}
momentPar ::
VU.Vector Int ->
VU.Vector Int ->
Int ->
VU.Vector Double ->
VU.Vector Double ->
Int ->
VU.Vector Int ->
IO Moments
momentPar vis offs nGroups xs ys caps bounds = do
cnt <- VUM.replicate nGroups (0 :: Int)
sx <- VUM.replicate nGroups (0 :: Double)
sy <- VUM.replicate nGroups (0 :: Double)
sxx <- VUM.replicate nGroups (0 :: Double)
syy <- VUM.replicate nGroups (0 :: Double)
sxy <- VUM.replicate nGroups (0 :: Double)
let bump arr g d = VUM.unsafeRead arr g >>= \c -> VUM.unsafeWrite arr g (c + d)
forEachRange bounds caps $ \gs ge ->
overGroups vis offs gs ge $ \g row -> do
let !x = VU.unsafeIndex xs row
!y = VU.unsafeIndex ys row
VUM.unsafeRead cnt g >>= \c -> VUM.unsafeWrite cnt g (c + 1)
bump sx g x
bump sy g y
bump sxx g (x * x)
bump syy g (y * y)
bump sxy g (x * y)
Moments . fromUnboxedVector
<$> VU.unsafeFreeze cnt
<*> (fromUnboxedVector <$> VU.unsafeFreeze sx)
<*> (fromUnboxedVector <$> VU.unsafeFreeze sy)
<*> (fromUnboxedVector <$> VU.unsafeFreeze sxx)
<*> (fromUnboxedVector <$> VU.unsafeFreeze syy)
<*> (fromUnboxedVector <$> VU.unsafeFreeze sxy)