massiv-1.0.5.0: src/Data/Massiv/Array/Delayed/Windowed.hs
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
-- |
-- Module : Data.Massiv.Array.Delayed.Windowed
-- Copyright : (c) Alexey Kuleshevich 2018-2025
-- License : BSD3
-- Maintainer : Alexey Kuleshevich <lehins@yandex.ru>
-- Stability : experimental
-- Portability : non-portable
module Data.Massiv.Array.Delayed.Windowed (
DW (..),
Array (..),
Window (..),
insertWindow,
getWindow,
dropWindow,
makeWindowedArray,
) where
import Control.Monad (when)
import Control.Scheduler (trivialScheduler_)
import Data.Massiv.Array.Delayed.Pull
import Data.Massiv.Array.Manifest.Boxed
import Data.Massiv.Array.Manifest.Internal
import Data.Massiv.Core
import Data.Massiv.Core.Common
import Data.Massiv.Core.List (showArrayList, showsArrayPrec)
import Data.Maybe (fromMaybe)
import GHC.Base (modInt)
import GHC.TypeLits
-- | Delayed Windowed Array representation.
data DW = DW
data Window ix e = Window
{ windowStart :: !ix
-- ^ Index of where window will start at.
, windowSize :: !(Sz ix)
-- ^ Size of the window
, windowIndex :: ix -> e
-- ^ Indexing function for the window
, windowUnrollIx2 :: !(Maybe Int)
-- ^ Setting this value during stencil application improves cache
-- utilization by unrolling the loop for Ix2 and higher dimensions.
-- Has no affect on arrays with one dimension.
}
instance Functor (Window ix) where
fmap f arr@Window{windowIndex} = arr{windowIndex = f . windowIndex}
data instance Array DW ix e = DWArray
{ dwComp :: !Comp
, dwSize :: !(Sz ix)
, dwIndex :: ix -> e
, dwWindow :: !(Maybe (Window ix e))
}
instance (Ragged L ix e, Load DW ix e, Show e) => Show (Array DW ix e) where
showsPrec = showsArrayPrec (computeAs B)
showList = showArrayList
instance Strategy DW where
setComp c arr = arr{dwComp = c}
{-# INLINE setComp #-}
getComp = dwComp
{-# INLINE getComp #-}
repr = DW
instance Functor (Array DW ix) where
fmap f arr@DWArray{dwIndex, dwWindow} =
arr
{ dwIndex = f . dwIndex
, dwWindow = fmap f <$> dwWindow
}
{-# INLINE fmap #-}
--
--
-- @since 0.3.0
-- _makeWindowedArrayM
-- :: Source r ix e
-- => Array r ix e -- ^ Source array that will have a window inserted into it
-- -> ix -- ^ Start index for the window
-- -> Sz ix -- ^ Size of the window
-- -> (ix -> e) -- ^ Inside window indexing function
-- -> Array DW ix e
-- _makeWindowedArrayM !arr !windowStart !windowSize windowIndex
-- | not (isSafeIndex sz windowStart) =
-- error $
-- "makeWindowedArray: Incorrect window starting index: (" ++
-- show windowStart ++ ") for array size: (" ++ show (size arr) ++ ")"
-- | totalElem windowSize == 0 =
-- error $
-- "makeWindowedArray: Window can't hold any elements with this size: (" ++ show windowSize ++ ")"
-- | not
-- (isSafeIndex
-- (Sz (liftIndex (+ 1) (unSz sz)))
-- (liftIndex2 (+) windowStart (unSz windowSize))) =
-- error $
-- "makeWindowedArray: Incorrect window size: (" ++
-- show windowSize ++
-- ") and/or starting index: (" ++
-- show windowStart ++ ") for array size: (" ++ show (size arr) ++ ")"
-- | otherwise =
-- DWArray {dwArray = delay arr, dwWindow = Just $! Window {..}}
-- where
-- windowUnrollIx2 = Nothing
-- sz = size arr
-- {-# INLINE _makeWindowedArrayM #-}
-- | Construct a delayed windowed array by supply a separate element producing function for the
-- interior of an array. This is very usful for stencil mapping, where interior function does not
-- perform boundary checks, thus significantly speeding up computation process.
--
-- @since 0.1.3
makeWindowedArray
:: (Index ix, Source r e)
=> Array r ix e
-- ^ Source array that will have a window inserted into it
-> ix
-- ^ Start index for the window
-> Sz ix
-- ^ Size of the window
-> (ix -> e)
-- ^ Indexing function foto use inside window
-> Array DW ix e
makeWindowedArray !arr wStart wSize wIndex =
insertWindow (delay arr) $
Window{windowStart = wStart, windowSize = wSize, windowIndex = wIndex, windowUnrollIx2 = Nothing}
{-# INLINE makeWindowedArray #-}
-- | Inserts a `Window` into a delayed array while scaling the window down if it doesn't fit inside
-- that array.
--
-- @since 0.3.0
insertWindow
:: Index ix
=> Array D ix e
-- ^ Source array that will have a window inserted into it
-> Window ix e
-- ^ Window to place inside the delayed array
-> Array DW ix e
insertWindow !arr !window =
DWArray
{ dwComp = getComp arr
, dwSize = arrSize
, dwIndex = unsafeIndex arr
, dwWindow =
Just $!
Window
{ windowStart = wStart'
, windowSize = Sz (liftIndex2 min wSize (liftIndex2 (-) sz wStart'))
, windowIndex = wIndex
, windowUnrollIx2 = wUnrollIx2
}
}
where
wStart' = unSz (Sz (liftIndex2 min wStart (liftIndex (subtract 1) sz)))
arrSize@(Sz sz) = size arr
Window
{ windowStart = wStart
, windowSize = Sz wSize
, windowIndex = wIndex
, windowUnrollIx2 = wUnrollIx2
} = window
{-# INLINE insertWindow #-}
-- | Get the `Window` from a windowed array.
--
-- @since 0.2.1
getWindow :: Array DW ix e -> Maybe (Window ix e)
getWindow = dwWindow
{-# INLINE getWindow #-}
-- | Drop the `Window` from a windowed array.
--
-- @since 0.3.0
dropWindow :: Array DW ix e -> Array D ix e
dropWindow DWArray{..} =
DArray
{ dComp = dwComp
, dSize = dwSize
, dPrefIndex = PrefIndex dwIndex
}
{-# INLINE dropWindow #-}
zeroWindow :: Index ix => Window ix e
zeroWindow = Window zeroIndex zeroSz windowError Nothing
{-# INLINE zeroWindow #-}
data EmptyWindowException = EmptyWindowException deriving (Eq, Show)
instance Exception EmptyWindowException where
displayException _ = "Index of zero size Window"
windowError :: ix -> a
windowError _ = throwImpossible EmptyWindowException
{-# NOINLINE windowError #-}
loadWithIx1
:: Monad m
=> (m () -> m ())
-> Array DW Ix1 e
-> (Ix1 -> e -> m a)
-> m (Ix1 -> Ix1 -> m (), Ix1, Ix1)
loadWithIx1 with (DWArray _ sz uIndex mWindow) uWrite = do
let Window it wk uwIndex _ = fromMaybe zeroWindow mWindow
wEnd = it + unSz wk
with $ iterA_ 0 it 1 (<) $ \ !i -> uWrite i (uIndex i)
with $ iterA_ wEnd (unSz sz) 1 (<) $ \ !i -> uWrite i (uIndex i)
return (\from to -> with $ iterA_ from to 1 (<) $ \ !i -> uWrite i (uwIndex i), it, wEnd)
{-# INLINE loadWithIx1 #-}
instance Index ix => Shape DW ix where
maxLinearSize = Just . linearSize
{-# INLINE maxLinearSize #-}
linearSize = SafeSz . totalElem . dwSize
{-# INLINE linearSize #-}
outerSize = dwSize
{-# INLINE outerSize #-}
instance Load DW Ix1 e where
makeArray c sz f = DWArray c sz f Nothing
{-# INLINE makeArray #-}
iterArrayLinearST_ scheduler arr uWrite = do
(loadWindow, wStart, wEnd) <- loadWithIx1 (scheduleWork scheduler) arr uWrite
let (chunkWidth, slackWidth) = (wEnd - wStart) `quotRem` numWorkers scheduler
loopA_ 0 (< numWorkers scheduler) (+ 1) $ \ !wid ->
let !it' = wid * chunkWidth + wStart
in loadWindow it' (it' + chunkWidth)
when (slackWidth > 0) $
let !itSlack = numWorkers scheduler * chunkWidth + wStart
in loadWindow itSlack (itSlack + slackWidth)
{-# INLINE iterArrayLinearST_ #-}
instance StrideLoad DW Ix1 e where
iterArrayLinearWithStrideST_ scheduler stride sz arr uWrite = do
(loadWindow, (wStart, wEnd)) <- loadArrayWithIx1 (scheduleWork scheduler) arr stride sz uWrite
let (chunkWidth, slackWidth) = (wEnd - wStart) `quotRem` numWorkers scheduler
loopA_ 0 (< numWorkers scheduler) (+ 1) $ \ !wid ->
let !it' = wid * chunkWidth + wStart
in loadWindow (it', it' + chunkWidth)
when (slackWidth > 0) $
let !itSlack = numWorkers scheduler * chunkWidth + wStart
in loadWindow (itSlack, itSlack + slackWidth)
{-# INLINE iterArrayLinearWithStrideST_ #-}
loadArrayWithIx1
:: Monad m
=> (m () -> m ())
-> Array DW Ix1 e
-> Stride Ix1
-> Sz1
-> (Ix1 -> e -> m a)
-> m ((Ix1, Ix1) -> m (), (Ix1, Ix1))
loadArrayWithIx1 with (DWArray _ arrSz uIndex mWindow) stride _ uWrite = do
let Window it wk uwIndex _ = fromMaybe zeroWindow mWindow
wEnd = it + unSz wk
strideIx = unStride stride
with $ iterA_ 0 it strideIx (<) $ \ !i -> uWrite (i `div` strideIx) (uIndex i)
with $
iterA_ (strideStart stride wEnd) (unSz arrSz) strideIx (<) $ \ !i ->
uWrite (i `div` strideIx) (uIndex i)
return
( \(from, to) ->
with $
iterA_ (strideStart stride from) to strideIx (<) $ \ !i ->
uWrite (i `div` strideIx) (uwIndex i)
, (it, wEnd)
)
{-# INLINE loadArrayWithIx1 #-}
loadWithIx2
:: Monad m
=> (m () -> m ())
-> Array DW Ix2 t1
-> (Int -> t1 -> m ())
-> m (Ix2 -> m (), Ix2)
loadWithIx2 with arr uWrite = do
let DWArray _ (Sz (m :. n)) uIndex window = arr
Window (it :. jt) (Sz (wm :. wn)) uwIndex mUnrollHeight = fromMaybe zeroWindow window
ib :. jb = (wm + it) :. (wn + jt)
!blockHeight = maybe 1 (min 7 . max 1) mUnrollHeight
!sz = strideSize oneStride $ outerSize arr
writeB !ix = uWrite (toLinearIndex sz ix) (uIndex ix)
{-# INLINE writeB #-}
writeW !ix = uWrite (toLinearIndex sz ix) (uwIndex ix)
{-# INLINE writeW #-}
with $ iterA_ (0 :. 0) (it :. n) (1 :. 1) (<) writeB
with $ iterA_ (ib :. 0) (m :. n) (1 :. 1) (<) writeB
with $ iterA_ (it :. 0) (ib :. jt) (1 :. 1) (<) writeB
with $ iterA_ (it :. jb) (ib :. n) (1 :. 1) (<) writeB
let f (it' :. ib') = with $ unrollAndJam blockHeight (it' :. jt) (ib' :. jb) 1 writeW
{-# INLINE f #-}
return (f, it :. ib)
{-# INLINE loadWithIx2 #-}
loadArrayWithIx2
:: Monad m
=> (m () -> m ())
-> Array DW Ix2 e
-> Stride Ix2
-> Sz2
-> (Int -> e -> m ())
-> m (Ix2 -> m (), Ix2)
loadArrayWithIx2 with arr stride sz uWrite = do
let DWArray _ (Sz (m :. n)) uIndex window = arr
Window (it :. jt) (Sz (wm :. wn)) uwIndex mUnrollHeight = fromMaybe zeroWindow window
ib :. jb = (wm + it) :. (wn + jt)
!blockHeight = maybe 1 (min 7 . max 1) mUnrollHeight
strideIx@(is :. js) = unStride stride
writeB !ix = uWrite (toLinearIndexStride stride sz ix) (uIndex ix)
{-# INLINE writeB #-}
writeW !ix = uWrite (toLinearIndexStride stride sz ix) (uwIndex ix)
{-# INLINE writeW #-}
with $ iterA_ (0 :. 0) (it :. n) strideIx (<) writeB
with $ iterA_ (strideStart stride (ib :. 0)) (m :. n) strideIx (<) writeB
with $ iterA_ (strideStart stride (it :. 0)) (ib :. jt) strideIx (<) writeB
with $ iterA_ (strideStart stride (it :. jb)) (ib :. n) strideIx (<) writeB
let f (it' :. ib')
| is > 1 || blockHeight <= 1 =
-- Turn off unrolling for vertical strides
iterA_ (strideStart stride (it' :. jt)) (ib' :. jb) strideIx (<) writeW
| otherwise =
unrollAndJam blockHeight (strideStart stride (it' :. jt)) (ib' :. jb) js writeW
{-# INLINE f #-}
return (with . f, it :. ib)
{-# INLINE loadArrayWithIx2 #-}
loadWindowIx2 :: Monad m => Int -> (Ix2 -> m ()) -> Ix2 -> m ()
loadWindowIx2 nWorkers loadWindow (it :. ib) = do
let !(chunkHeight, slackHeight) = (ib - it) `quotRem` nWorkers
loopA_ 0 (< nWorkers) (+ 1) $ \ !wid ->
let !it' = wid * chunkHeight + it
in loadWindow (it' :. (it' + chunkHeight))
when (slackHeight > 0) $
let !itSlack = nWorkers * chunkHeight + it
in loadWindow (itSlack :. (itSlack + slackHeight))
{-# INLINE loadWindowIx2 #-}
instance Load DW Ix2 e where
makeArray c sz f = DWArray c sz f Nothing
{-# INLINE makeArray #-}
iterArrayLinearST_ scheduler arr uWrite =
loadWithIx2 (scheduleWork scheduler) arr uWrite
>>= uncurry (loadWindowIx2 (numWorkers scheduler))
{-# INLINE iterArrayLinearST_ #-}
instance StrideLoad DW Ix2 e where
iterArrayLinearWithStrideST_ scheduler stride sz arr uWrite =
loadArrayWithIx2 (scheduleWork scheduler) arr stride sz uWrite
>>= uncurry (loadWindowIx2 (numWorkers scheduler))
{-# INLINE iterArrayLinearWithStrideST_ #-}
instance (Index (IxN n), Load DW (Ix (n - 1)) e) => Load DW (IxN n) e where
makeArray c sz f = DWArray c sz f Nothing
{-# INLINE makeArray #-}
iterArrayLinearST_ = loadWithIxN
{-# INLINE iterArrayLinearST_ #-}
instance (Index (IxN n), StrideLoad DW (Ix (n - 1)) e) => StrideLoad DW (IxN n) e where
iterArrayLinearWithStrideST_ = loadArrayWithIxN
{-# INLINE iterArrayLinearWithStrideST_ #-}
loadArrayWithIxN
:: (Index ix, StrideLoad DW (Lower ix) e)
=> Scheduler s ()
-> Stride ix
-> Sz ix
-> Array DW ix e
-> (Int -> e -> ST s ())
-> ST s ()
loadArrayWithIxN scheduler stride szResult arr uWrite = do
let DWArray _ sz uIndex window = arr
Window{windowStart, windowSize, windowIndex, windowUnrollIx2} = fromMaybe zeroWindow window
!(!headSourceSize, !lowerSourceSize) = unconsSz sz
!lowerSize = snd $ unconsSz szResult
!(!s, !lowerStrideIx) = unconsDim $ unStride stride
!(!curWindowStart, lowerWindowStart) = unconsDim windowStart
!(!headWindowSz, tailWindowSz) = unconsSz windowSize
!curWindowEnd = curWindowStart + unSz headWindowSz
!pageElements = totalElem lowerSize
lowerWindow =
Window
{ windowStart = lowerWindowStart
, windowSize = tailWindowSz
, windowIndex = \_ -> error "Window index uninitialized"
, windowUnrollIx2 = windowUnrollIx2
}
mkLowerWindow !i =
lowerWindow
{ windowIndex = windowIndex . consDim i
}
loadLower mkWindow !i =
let !lowerArray =
DWArray
{ dwComp = Seq
, dwSize = lowerSourceSize
, dwIndex = uIndex . consDim i
, dwWindow = mkWindow i
}
!innerScheduler =
if numWorkers scheduler <= unSz (strideSize (Stride s) headSourceSize)
then trivialScheduler_
else scheduler
in scheduleWork_ scheduler $
iterArrayLinearWithStrideST_ innerScheduler (Stride lowerStrideIx) lowerSize lowerArray $ \k ->
uWrite (k + pageElements * (i `div` s))
{-# INLINE loadLower #-}
loopA_ 0 (< headDim windowStart) (+ s) (loadLower (const Nothing))
loopA_
(strideStart (Stride s) curWindowStart)
(< curWindowEnd)
(+ s)
(loadLower (Just . mkLowerWindow))
loopA_
(strideStart (Stride s) curWindowEnd)
(< unSz headSourceSize)
(+ s)
(loadLower (const Nothing))
{-# INLINE loadArrayWithIxN #-}
loadWithIxN
:: (Index ix, Load DW (Lower ix) e)
=> Scheduler s ()
-> Array DW ix e
-> (Int -> e -> ST s ())
-> ST s ()
loadWithIxN scheduler arr uWrite = do
let DWArray _ sz uIndex window = arr
Window{windowStart, windowSize, windowIndex, windowUnrollIx2} = fromMaybe zeroWindow window
!(!si, !szL) = unconsSz sz
!windowEnd = liftIndex2 (+) windowStart (unSz windowSize)
!(!t, windowStartL) = unconsDim windowStart
!pageElements = totalElem szL
lowerWindow =
Window
{ windowStart = windowStartL
, windowSize = snd $ unconsSz windowSize
, windowIndex = \_ -> error "Window index uninitialized"
, windowUnrollIx2 = windowUnrollIx2
}
mkLowerWindow !i =
lowerWindow
{ windowIndex = windowIndex . consDim i
}
loadLower mkWindow !i =
let !lowerArray =
DWArray
{ dwComp = Seq
, dwSize = szL
, dwIndex = uIndex . consDim i
, dwWindow = mkWindow i
}
!innerScheduler =
if numWorkers scheduler <= unSz si
then trivialScheduler_
else scheduler
in scheduleWork_ scheduler $
iterArrayLinearST_ innerScheduler lowerArray (\k -> uWrite (k + pageElements * i))
{-# INLINE loadLower #-}
loopA_ 0 (< headDim windowStart) (+ 1) (loadLower (const Nothing))
loopA_ t (< headDim windowEnd) (+ 1) (loadLower (Just . mkLowerWindow))
loopA_ (headDim windowEnd) (< unSz si) (+ 1) (loadLower (const Nothing))
{-# INLINE loadWithIxN #-}
unrollAndJam
:: Monad m
=> Int
-- ^ Block height. Must not be zero.
-> Ix2
-- ^ Top corner
-> Ix2
-- ^ Bottom corner
-> Int
-- ^ Column Stride
-> (Ix2 -> m ())
-- ^ Writing function
-> m ()
unrollAndJam !bH (it :. jt) (ib :. jb) js f = do
let
f2 (i :. j) = f (i :. j) >> f ((i + 1) :. j)
f3 (i :. j) = f (i :. j) >> f2 ((i + 1) :. j)
f4 (i :. j) = f (i :. j) >> f3 ((i + 1) :. j)
f5 (i :. j) = f (i :. j) >> f4 ((i + 1) :. j)
f6 (i :. j) = f (i :. j) >> f5 ((i + 1) :. j)
f7 (i :. j) = f (i :. j) >> f6 ((i + 1) :. j)
f' = case bH of
1 -> f
2 -> f2
3 -> f3
4 -> f4
5 -> f5
6 -> f6
_ -> f7
!ibS = ib - ((ib - it) `modInt` bH)
loopA_ it (< ibS) (+ bH) $ \ !i ->
loopA_ jt (< jb) (+ js) $ \ !j ->
f' (i :. j)
loopA_ ibS (< ib) (+ 1) $ \ !i ->
loopA_ jt (< jb) (+ js) $ \ !j ->
f (i :. j)
{-# INLINE unrollAndJam #-}
-- TODO: Implement Hilbert curve