on master help sign in
change writeNUnsafe to use strict unboxed data
1b8439dHarendra Kumar
design/inlining.md+12
@@ -109,3 +109,15 @@ on `i` to get it specialized:
109```
109```
110
110
111`-flate-specialise` also helps in this case.
111`-flate-specialise` also helps in this case.
112+
113+## Stream and Fold State Data Structures
114+
115+Since state is an internal data structure threaded around in the loop, it is a
116+good practice to use strict unboxed fields for state data structures where
117+possible. In most cases it is not necessary, but in some cases it may affect
118+fusion and make a difference of 10x performance or more. For example, using
119+non-strict fields can increase the code size for internal join points and
120+functions created during transformations, which can affect the inlining of
121+these code blocks which in turn can affect stream fusion.
122+
123+See https://gitlab.haskell.org/ghc/ghc/issues/17075 .
src/Streamly/Benchmark/FileIO/Stream.hs+1
@@ -165,6 +165,7 @@ hinspect $ hasNoTypeClasses 'copyCodecUtf8
165{-# INLINE chunksOf #-}
165{-# INLINE chunksOf #-}
166chunksOf :: Int -> Handle -> IO Int
166chunksOf :: Int -> Handle -> IO Int
167chunksOf n inh =
167chunksOf n inh =
168+ -- writeNUnsafe gives 2.5x boost here over writeN.
168 S.length $ S.chunksOf n (AT.writeNUnsafe n) (FH.read inh)
169 S.length $ S.chunksOf n (AT.writeNUnsafe n) (FH.read inh)
169
170
170hinspect $ hasNoTypeClasses 'chunksOf
171hinspect $ hasNoTypeClasses 'chunksOf
src/Streamly/FileSystem/Handle/Internal.hs+2−12
@@ -91,16 +91,6 @@ writeSArraysInChunksOf :: (MonadIO m, Storable a)
91 => Int -> Handle -> SerialT m (Array a) -> m ()
91 => Int -> Handle -> SerialT m (Array a) -> m ()
92writeSArraysInChunksOf n h xs = writeSArrays h $ AS.compact n xs
92writeSArraysInChunksOf n h xs = writeSArrays h $ AS.compact n xs
93
93
94−-- XXX Using custom array chunking with A.arraysOf, writeS gives 2x faster
95−-- readStream/catStream benchmark compared to S.arraysOf based writeS as well
96−-- as compared to the fold IO version of cat benchmark i.e. readStream/cat.
97−-- However, if we use the most optimal A.writeNUnsafe fold then the fold
98−-- version of cat is also as fast. But with the most optimal version of
99−-- A.writeNUnsafe, fusion breaks. Once GHC fusion problem gets fixed we should
100−-- be able to use that.
101−--
102−-- Until then we are keeping the custom A.arraysOf code.
103−--
104-- | @writeSInChunksOf chunkSize handle stream@ writes @stream@ to @handle@ in
94-- | @writeSInChunksOf chunkSize handle stream@ writes @stream@ to @handle@ in
105-- chunks of @chunkSize@. A write is performed to the IO device as soon as we
95-- chunks of @chunkSize@. A write is performed to the IO device as soon as we
106-- collect the required input size.
96-- collect the required input size.
@@ -108,8 +98,8 @@ writeSArraysInChunksOf n h xs = writeSArrays h $ AS.compact n xs
108-- @since 0.7.0
98-- @since 0.7.0
109{-# INLINE writeSInChunksOf #-}
99{-# INLINE writeSInChunksOf #-}
110writeSInChunksOf :: MonadIO m => Int -> Handle -> SerialT m Word8 -> m ()
100writeSInChunksOf :: MonadIO m => Int -> Handle -> SerialT m Word8 -> m ()
111−-- writeSInChunksOf n h m = writeSArrays h $ S.arraysOf n m
101+writeSInChunksOf n h m = writeSArrays h $ S.arraysOf n m
112−writeSInChunksOf n h m = writeSArrays h $ AS.arraysOf n m
102+-- writeSInChunksOf n h m = writeSArrays h $ AS.arraysOf n m
113
103
114-- > write = 'writeInChunksOf' A.defaultChunkSize
104-- > write = 'writeInChunksOf' A.defaultChunkSize
115--
105--
src/Streamly/Memory/Array/Types.hs+8−25
@@ -520,6 +520,10 @@ writeN n = Fold step initial extract
520 return $ Array start (end `plusPtr` sizeOf (undefined :: a)) bound
520 return $ Array start (end `plusPtr` sizeOf (undefined :: a)) bound
521 extract = return -- liftIO . shrinkToFit
521 extract = return -- liftIO . shrinkToFit
522
522
523+data ArrayUnsafe a = ArrayUnsafe
524+ {-# UNPACK #-} !(ForeignPtr a) -- first address
525+ {-# UNPACK #-} !(Ptr a) -- first unused address
526+
523-- | Like 'writeN' but does not check the array bounds when writing. The fold
527-- | Like 'writeN' but does not check the array bounds when writing. The fold
524-- driver must not call the step function more than 'n' times otherwise it will
528-- driver must not call the step function more than 'n' times otherwise it will
525-- corrupt the memory and crash. This function exists mainly because any
529-- corrupt the memory and crash. This function exists mainly because any
@@ -534,34 +538,13 @@ writeNUnsafe n = Fold step initial extract
534
538
535 where
539 where
536
540
537− {-
538− -- XXX this implementation is good on all benchmarks except
539− -- readStream/catStream which uses a stream based writeS instead of a fold
540− -- based write. In the writeS case it does not fuse and therefore performs
541− -- 10x worse.
542 initial = do
541 initial = do
543 (Array start end _) <- liftIO $ newArray (max n 0)
542 (Array start end _) <- liftIO $ newArray (max n 0)
544− return (start, end)
543+ return $ ArrayUnsafe start end
545− -- XXX any if condition in the step causes fusion to fail with groupsOf
546− -- If we need to do bounds check we can use 'n' instead of threading around
547− -- bound pointer.
548− step (start, end) x = do
549− liftIO $ poke end x
550− return $ (start, (end `plusPtr` sizeOf (undefined :: a)))
551− extract (start, end) = return $ Array start end end -- liftIO . shrinkToFit
552− -}
553− -- This implementation fuses in all known cases as of now. Even though both
554− -- readStream/cat and readStream/catStream benchmarks perform 2x worse when
555− -- arraysOf uses this impl compared to their best seen performance.
556− -- readStream/cat is best with the above implementation and
557− -- readStream/catStream is best when using the custom arraysOf
558− -- implementation.
559− initial = do
560− liftIO $ newArray (max n 0)
561− step (Array start end bound) x = do
544+ step (ArrayUnsafe start end) x = do
562 liftIO $ poke end x
545 liftIO $ poke end x
563− return $ Array start (end `plusPtr` sizeOf (undefined :: a)) bound
546+ return $ (ArrayUnsafe start (end `plusPtr` sizeOf (undefined :: a)))
564− extract = return -- liftIO . shrinkToFit
547+ extract (ArrayUnsafe start end) = return $ Array start end end -- liftIO . shrinkToFit
565
548
566{-# INLINE_NORMAL fromStreamDN #-}
549{-# INLINE_NORMAL fromStreamDN #-}
567fromStreamDN :: forall m a. (MonadIO m, Storable a)
550fromStreamDN :: forall m a. (MonadIO m, Storable a)