massiv-1.0.2.0: src/Data/Massiv/Core/Index/Iterator.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE PatternSynonyms #-}
-- |
-- Module : Data.Massiv.Core.Index.Iterator
-- Copyright : (c) Alexey Kuleshevich 2021-2022
-- License : BSD3
-- Maintainer : Alexey Kuleshevich <lehins@yandex.ru>
-- Stability : experimental
-- Portability : non-portable
--
module Data.Massiv.Core.Index.Iterator
( Iterator(..)
-- * Extra iterator functions
, iterTargetAccST
, iterTargetAccST_
, iterTargetFullWithStrideAccST
, iterTargetFullWithStrideAccST_
, iterTargetST_
, iterTargetFullWithStrideST_
-- * Iterator implementations
, RowMajor(RowMajor)
, defRowMajor
, RowMajorLinear(RowMajorLinear)
, defRowMajorLinear
, RowMajorUnbalanced(RowMajorUnbalanced)
, defRowMajorUnbalanced
) where
import Control.Monad
import Control.Monad.ST
import Control.Scheduler
import Data.Massiv.Core.Index.Internal
import Data.Massiv.Core.Index.Stride
import Data.Massiv.Core.Loop
class Iterator it where
{-# MINIMAL (iterTargetM, iterTargetA_, iterTargetWithStrideAccST, iterTargetWithStrideAccST_) #-}
-- | Iterate over a target region using linear index with access to the source
-- index, which adjusted according to the stride. Use `iterTargetM` if you
-- need an accumulator.
--
-- @since 1.0.2
iterTargetA_ ::
(Index ix, Applicative f)
=> it
-> Int -- ^ Target linear index start
-> Sz ix -- ^ Target size
-> ix -- ^ Source start index
-> Stride ix -- ^ Source stride
-> (Ix1 -> ix -> f a)
-- ^ Action that accepts a linear index of the target and multi-dimensional
-- index of the source.
-> f ()
-- | Iterate over a target region using linear index with access to the source
-- index, which adjusted according to the stride.
--
-- @since 1.0.2
iterTargetM ::
(Index ix, Monad m)
=> it
-> Ix1 -- ^ Target linear index start
-> Sz ix -- ^ Target size
-> ix -- ^ Source start index
-> Stride ix -- ^ Source stride
-> a -- ^ Accumulator
-> (Ix1 -> ix -> a -> m a)
-- ^ Action that accepts a linear index of the target,
-- multi-dimensional index of the source and accumulator
-> m a
iterTargetWithStrideAccST ::
Index ix
=> it
-> Scheduler s a -- ^ Scheduler to use
-> Ix1 -- ^ Target linear start index
-> Sz ix -- ^ Target size
-> ix -- ^ Source start index
-> Stride ix -- ^ Source stride
-> a -- ^ Initial accumulator
-> (a -> ST s (a, a))
-- ^ Splitting action that produces new accumulators for separate worker threads.
-> (Ix1 -> ix -> a -> ST s a) -- ^ Action
-> ST s a
iterTargetWithStrideAccST_ ::
Index ix
=> it
-> Scheduler s () -- ^ Scheduler to use
-> Ix1 -- ^ Target linear start index
-> Sz ix -- ^ Target size
-> ix -- ^ Start
-> Stride ix -- ^ Stride
-> a -- ^ Initial accumulator
-> (a -> ST s (a, a))
-- ^ Splitting action that produces new accumulators for separate worker threads.
-> (Ix1 -> ix -> a -> ST s a) -- ^ Action
-> ST s ()
-- | Iterate over a region with a monadic action and accumulator.
--
-- @since 1.0.2
iterFullM ::
(Index ix, Monad m)
=> it
-> ix -- ^ Source start index
-> Sz ix -- ^ Source size
-> a -- ^ Accumulator
-> (ix -> a -> m a)
-- ^ Action that accepts a linear index of the target,
-- multi-dimensional index of the source and accumulator
-> m a
iterFullM it start sz acc f =
iterTargetM it 0 sz start oneStride acc (const f)
{-# INLINE iterFullM #-}
-- | Iterate over a region with an applicative action ignoring the result.
--
-- @since 1.0.2
iterFullA_ ::
(Index ix, Applicative f)
=> it
-> ix -- ^ Source start index
-> Sz ix -- ^ Source size
-> (ix -> f a)
-- ^ Action that accepts a linear index of the target,
-- multi-dimensional index of the source and accumulator
-> f ()
iterFullA_ it start sz f =
iterTargetA_ it 0 sz start oneStride (const f)
{-# INLINE iterFullA_ #-}
-- | Iterate over a region in a ST monad with access to `Scheduler`.
iterFullAccST ::
Index ix
=> it -- ^ Scheduler multiplying factor. Must be positive
-> Scheduler s a -- ^ Scheduler to use
-> ix -- ^ Start index
-> Sz ix -- ^ Size
-> a -- ^ Initial accumulator
-> (a -> ST s (a, a)) -- ^ Function that splits accumulator for each scheduled job.
-> (ix -> a -> ST s a) -- ^ Action
-> ST s a
iterFullAccST it scheduler start sz acc splitAcc f =
iterTargetAccST it scheduler 0 sz start acc splitAcc (const f)
{-# INLINE iterFullAccST #-}
iterTargetFullAccST ::
Index ix
=> it
-> Scheduler s a -- ^ Scheduler to use
-> Ix1 -- ^ Target linear start index
-> Sz ix -- ^ Target size
-> a -- ^ Initial accumulator
-> (a -> ST s (a, a)) -- ^ Function that splits accumulator for each scheduled job.
-> (Ix1 -> ix -> a -> ST s a) -- ^ Action
-> ST s a
iterTargetFullAccST it scheduler iStart sz =
iterTargetFullWithStrideAccST it scheduler iStart sz oneStride
{-# INLINE iterTargetFullAccST #-}
iterTargetFullAccST_ ::
Index ix
=> it
-> Scheduler s () -- ^ Scheduler to use
-> Ix1 -- ^ Target linear start index
-> Sz ix -- ^ Target size
-> a -- ^ Initial accumulator
-> (a -> ST s (a, a)) -- ^ Function that splits accumulator for each scheduled job.
-> (Ix1 -> ix -> a -> ST s a) -- ^ Action
-> ST s ()
iterTargetFullAccST_ it scheduler iStart sz =
iterTargetFullWithStrideAccST_ it scheduler iStart sz oneStride
{-# INLINE iterTargetFullAccST_ #-}
iterTargetFullST_ ::
Index ix
=> it
-> Scheduler s () -- ^ Scheduler to use
-> Ix1 -- ^ Target linear start index
-> Sz ix -- ^ Target size
-> (Ix1 -> ix -> ST s ()) -- ^ Action
-> ST s ()
iterTargetFullST_ it scheduler iStart sz =
iterTargetST_ it scheduler iStart sz (pureIndex 0)
{-# INLINE iterTargetFullST_ #-}
-- NOTE: this function does not have to be part of the class, but for some
-- reason it creates a severe regression when moved outside.
-- | Iterate over a target array with a stride without an accumulator
iterTargetWithStrideST_ ::
Index ix
=> it
-> Scheduler s () -- ^ Scheduler to use
-> Ix1 -- ^ Target linear start index
-> Sz ix -- ^ Target size
-> ix -- ^ Start
-> Stride ix -- ^ Stride
-> (Ix1 -> ix -> ST s a) -- ^ Action
-> ST s ()
iterTargetWithStrideST_ it scheduler i sz ix stride action =
iterTargetWithStrideAccST_ it scheduler i sz ix stride () noSplit $ \j jx _ ->
void $ action j jx
{-# INLINE iterTargetWithStrideST_ #-}
-- | Default iterator that parallelizes work in linear chunks. Supplied factor
-- will be used to schedule that many jobs per capability.
--
-- @since 1.0.2
newtype RowMajor = RowMajorInternal Int
-- | Default row major iterator with multiplying factor set to @8@.
defRowMajor :: RowMajor
defRowMajor = RowMajorInternal 8
pattern RowMajor :: Int
-- ^ Multiplier that will be used to scale number of jobs.
-> RowMajor
pattern RowMajor f <- RowMajorInternal f
where RowMajor = RowMajorInternal . max 1
{-# COMPLETE RowMajor #-}
instance Iterator RowMajor where
iterFullM _ start (Sz sz) = iterM start sz (pureIndex 1) (<)
{-# INLINE iterFullM #-}
iterFullA_ _ start (Sz sz) = iterA_ start sz (pureIndex 1) (<)
{-# INLINE iterFullA_ #-}
iterFullAccST (RowMajorInternal fact) scheduler startIx =
iterRowMajorST fact scheduler startIx (pureIndex 1)
{-# INLINE iterFullAccST #-}
iterTargetA_ _ i sz start (Stride stride) =
iterTargetRowMajorA_ 0 i sz start stride
{-# INLINE iterTargetA_ #-}
iterTargetM _ i sz start (Stride stride) =
iterTargetRowMajorAccM 0 i sz start stride
{-# INLINE iterTargetM #-}
iterTargetWithStrideAccST (RowMajor fact) scheduler i sz ix (Stride stride) =
iterTargetRowMajorAccST 0 fact scheduler i sz ix stride
{-# INLINE iterTargetWithStrideAccST #-}
iterTargetWithStrideAccST_ (RowMajor fact) scheduler i sz ix (Stride stride) =
iterTargetRowMajorAccST_ 0 fact scheduler i sz ix stride
{-# INLINE iterTargetWithStrideAccST_ #-}
newtype RowMajorLinear = RowMajorLinear Int
defRowMajorLinear :: RowMajorLinear
defRowMajorLinear = RowMajorLinear 8
instance Iterator RowMajorLinear where
iterTargetM _ iStart sz start (Stride stride) acc action =
loopM 0 (< totalElem sz) (+ 1) acc $ \i ->
action (iStart + i) (liftIndex2 (+) start (liftIndex2 (*) stride (fromLinearIndex sz i)))
{-# INLINE iterTargetM #-}
iterTargetA_ _ iStart sz start (Stride stride) action =
loopA_ 0 (< totalElem sz) (+ 1) $ \i ->
action (iStart + i) (liftIndex2 (+) start (liftIndex2 (*) stride (fromLinearIndex sz i)))
{-# INLINE iterTargetA_ #-}
iterTargetFullAccST it scheduler iStart sz acc splitAcc action =
let !(RowMajorLinear fact) = it
in iterLinearAccST fact scheduler iStart 1 (totalElem sz) acc splitAcc $ \ !i ->
action i (fromLinearIndex sz i)
{-# INLINE iterTargetFullAccST #-}
iterTargetFullAccST_ it scheduler iStart sz acc splitAcc action =
let !(RowMajorLinear fact) = it
in iterLinearAccST_ fact scheduler iStart 1 (totalElem sz) acc splitAcc $ \ !i ->
action i (fromLinearIndex sz i)
{-# INLINE iterTargetFullAccST_ #-}
iterTargetFullST_ it scheduler iStart sz action =
let !(RowMajorLinear fact) = it
in iterLinearST_ fact scheduler iStart 1 (totalElem sz) $ \ !i ->
action i (fromLinearIndex sz i)
{-# INLINE iterTargetFullST_ #-}
iterTargetWithStrideAccST it scheduler iStart sz start (Stride stride) acc spliAcc action =
let RowMajorLinear fact = it
in iterLinearAccST fact scheduler 0 1 (totalElem sz) acc spliAcc $ \i ->
action (iStart + i) $
liftIndex2 (+) start (liftIndex2 (*) stride (fromLinearIndex sz i))
{-# INLINE iterTargetWithStrideAccST #-}
iterTargetWithStrideAccST_ it scheduler iStart sz start (Stride stride) acc spliAcc action =
let RowMajorLinear fact = it
in iterLinearAccST_ fact scheduler 0 1 (totalElem sz) acc spliAcc $ \i ->
action (iStart + i) $
liftIndex2 (+) start (liftIndex2 (*) stride (fromLinearIndex sz i))
{-# INLINE iterTargetWithStrideAccST_ #-}
-- | Parallelizing unbalanced computation (i.e. computing some elements of the
-- array is much more expensive then the others) it can be benefitial to
-- interleave iteration. Perfect example of this would be a ray tracer or the
-- Mandelbrot set.
--
-- iteration without parallelization is equivalent to `RowMajor`
--
-- @since 1.0.2
newtype RowMajorUnbalanced = RowMajorUnbalancedInternal Int
defRowMajorUnbalanced :: RowMajorUnbalanced
defRowMajorUnbalanced = RowMajorUnbalancedInternal 8
pattern RowMajorUnbalanced :: Int
-- ^ Multiplier that will be used to scale number of jobs.
-> RowMajorUnbalanced
pattern RowMajorUnbalanced f <- RowMajorUnbalancedInternal f
where RowMajorUnbalanced = RowMajorUnbalancedInternal . max 1
{-# COMPLETE RowMajorUnbalanced #-}
instance Iterator RowMajorUnbalanced where
iterFullM (RowMajorUnbalanced fact) = iterFullM (RowMajor fact)
{-# INLINE iterFullM #-}
iterFullA_ (RowMajorUnbalanced fact) = iterFullA_ (RowMajor fact)
{-# INLINE iterFullA_ #-}
iterTargetM (RowMajorUnbalanced fact) = iterTargetM (RowMajor fact)
{-# INLINE iterTargetM #-}
iterTargetA_ (RowMajorUnbalanced fact) = iterTargetA_ (RowMajor fact)
{-# INLINE iterTargetA_ #-}
iterTargetWithStrideAccST = iterUnbalancedTargetWithStride loopM
{-# INLINE iterTargetWithStrideAccST #-}
iterTargetWithStrideAccST_ it scheduler iStart sz start stride acc splitAcc' action =
void $
iterUnbalancedTargetWithStride innerLoop it scheduler iStart sz start stride acc splitAcc' action
where
innerLoop initial condition increment initAcc f =
void $ loopM initial condition increment initAcc f
{-# INLINE innerLoop #-}
{-# INLINE iterTargetWithStrideAccST_ #-}
iterUnbalancedTargetWithStride ::
Index ix
=> (Int -> (Int -> Bool) -> (Int -> Int) -> a -> (Int -> t) -> ST s b)
-> RowMajorUnbalanced
-> Scheduler s b
-> Int
-> Sz ix
-> ix
-> Stride ix
-> a
-> (a -> ST s (a, a))
-> (Int -> ix -> t)
-> ST s a
iterUnbalancedTargetWithStride innerLoop it scheduler iStart sz start stride acc splitAcc action =
let RowMajorUnbalanced fact = it
!n = totalElem sz
!step = min (fact * numWorkers scheduler) n
in loopM 0 (< step) (+ 1) acc $ \ !istep !a -> do
(curAcc, nextAcc) <- splitAcc a
scheduleMassivWork scheduler $
innerLoop istep (< n) (+ step) curAcc $ \i ->
action (iStart + i) $
liftIndex2 (+) start (liftIndex2 (*) (unStride stride) (fromLinearIndex sz i))
pure nextAcc
{-# INLINE iterUnbalancedTargetWithStride #-}
noSplit :: Applicative m => () -> m ((), ())
noSplit _ = pure ((), ())
iterTargetAccST ::
(Iterator it, Index ix)
=> it
-> Scheduler s a -- ^ Scheduler to use
-> Ix1 -- ^ Target linear start index
-> Sz ix -- ^ Target size
-> ix -- ^ Source start
-> a
-> (a -> ST s (a, a))
-> (Ix1 -> ix -> a -> ST s a) -- ^ Action
-> ST s a
iterTargetAccST it scheduler iStart sz ix =
iterTargetWithStrideAccST it scheduler iStart sz ix oneStride
{-# INLINE iterTargetAccST #-}
iterTargetAccST_ ::
(Iterator it, Index ix)
=> it
-> Scheduler s () -- ^ Scheduler to use
-> Ix1 -- ^ Target linear start index
-> Sz ix -- ^ Target size
-> ix -- ^ Source start
-> a
-> (a -> ST s (a, a))
-> (Ix1 -> ix -> a -> ST s a) -- ^ Action
-> ST s ()
iterTargetAccST_ it scheduler iStart sz ix =
iterTargetWithStrideAccST_ it scheduler iStart sz ix oneStride
{-# INLINE iterTargetAccST_ #-}
iterTargetFullWithStrideST_ ::
(Iterator it, Index ix)
=> it
-> Scheduler s () -- ^ Scheduler to use
-> Ix1 -- ^ Target linear start index
-> Sz ix -- ^ Target size
-> Stride ix -- ^ Stride
-> (Ix1 -> ix -> ST s ()) -- ^ Action
-> ST s ()
iterTargetFullWithStrideST_ it scheduler iStart sz =
iterTargetWithStrideST_ it scheduler iStart sz (pureIndex 0)
{-# INLINE iterTargetFullWithStrideST_ #-}
iterTargetST_ ::
(Iterator it, Index ix)
=> it
-> Scheduler s () -- ^ Scheduler to use
-> Ix1 -- ^ Target linear start index
-> Sz ix -- ^ Target size
-> ix -- ^ Start
-> (Ix1 -> ix -> ST s ()) -- ^ Action
-> ST s ()
iterTargetST_ it scheduler iStart sz ix =
iterTargetWithStrideST_ it scheduler iStart sz ix oneStride
{-# INLINE iterTargetST_ #-}
iterTargetFullWithStrideAccST ::
(Iterator it, Index ix)
=> it
-> Scheduler s a -- ^ Scheduler to use
-> Ix1 -- ^ Target linear start index
-> Sz ix -- ^ Target size
-> Stride ix -- ^ Stride
-> a
-> (a -> ST s (a, a))
-> (Ix1 -> ix -> a -> ST s a) -- ^ Action
-> ST s a
iterTargetFullWithStrideAccST it scheduler iStart sz =
iterTargetWithStrideAccST it scheduler iStart sz (pureIndex 0)
{-# INLINE iterTargetFullWithStrideAccST #-}
iterTargetFullWithStrideAccST_ ::
(Iterator it, Index ix)
=> it
-> Scheduler s () -- ^ Scheduler to use
-> Ix1 -- ^ Target linear start index
-> Sz ix -- ^ Target size
-> Stride ix -- ^ Stride
-> a
-> (a -> ST s (a, a))
-> (Ix1 -> ix -> a -> ST s a) -- ^ Action
-> ST s ()
iterTargetFullWithStrideAccST_ it scheduler iStart sz =
iterTargetWithStrideAccST_ it scheduler iStart sz (pureIndex 0)
{-# INLINE iterTargetFullWithStrideAccST_ #-}