on master help sign in
Optimize and rename joinInnerHash to joinInnerMap
commit66fe3d1ec940fcdd4bf642fd7be1afd3497e292e authorHarendra Kumar <harendra@composewell.com> dateFri 14 Jan 2022, 13:08 (5 years ago) parent9351485
4 files changed+52−40
benchmark/Streamly/Benchmark/Prelude/Serial/NestedStream.hs+5−5 src/Streamly/Internal/Data/Stream/IsStream/Top.hs+38−24 streamly.cabal+1−1 test/Streamly/Test/Prelude/Top.hs+8−10
benchmark/Streamly/Benchmark/Prelude/Serial/NestedStream.hs+5−5
@@ -428,11 +428,11 @@ joinInner :: Int -> Int -> Int -> IO ()
428428joinInner val1 val2 _ =
429429 S.drain $ Internal.joinInner (==) (mkStreamLen val1) $ mkStreamLen val2
430430
431−{-# INLINE joinInnerHash #-}
432−joinInnerHash :: Int -> Int -> Int -> IO ()
433−joinInnerHash val1 val2 _ =
431+{-# INLINE joinInnerMap #-}
432+joinInnerMap :: Int -> Int -> Int -> IO ()
433+joinInnerMap val1 val2 _ =
434434 S.drain $
435− Internal.joinInnerHash
435+ Internal.joinInnerMap
436436 (fmap toKvMap (mkStreamLen val1))
437437 (fmap toKvMap (mkStreamLen val2))
438438
@@ -441,7 +441,7 @@ o_n_heap_buffering value =
441441 [ bgroup "buffered"
442442 [
443443 benchIOSrc1 "joinInner" (joinInner sqrtVal sqrtVal)
444− , benchIOSrc1 "joinInnerHash" (joinInnerHash sqrtVal sqrtVal)
444+ , benchIOSrc1 "joinInnerMap" (joinInnerMap sqrtVal sqrtVal)
445445 ]
446446 ]
447447
src/Streamly/Internal/Data/Stream/IsStream/Top.hs+38−24
@@ -37,8 +37,8 @@ module Streamly.Internal.Data.Stream.IsStream.Top
3737 -- ** Join operations
3838 , crossJoin
3939 , joinInner
40+ , joinInnerMap
4041 , joinInnerMerge
41− , joinInnerHash
4242 , leftJoin
4343 , mergeLeftJoin
4444 , hashLeftJoin
@@ -80,7 +80,6 @@ import qualified Streamly.Internal.Data.Stream.IsStream.Reduce as Stream
8080import qualified Streamly.Internal.Data.Stream.IsStream.Transform as Stream
8181import qualified Streamly.Internal.Data.Stream.IsStream.Type as IsStream
8282
83−
8483import Prelude hiding (filter, zipWith, concatMap, concat)
8584
8685-- $setup
@@ -261,12 +260,17 @@ crossJoin s1 s2 = do
261260-- are equal by the given equality pedicate then return the tuple (a, b).
262261--
263262-- The second stream is evaluated multiple times. If the stream is a
264−-- consume-once stream then the caller should cache it in an 'Data.Array.Array'
265−-- before calling this function. Caching may also improve performance if the
266−-- stream is expensive to evaluate.
263+-- consume-once stream then the caller should cache it (e.g. in a
264+-- 'Data.Array.Array') before calling this function. Caching may also improve
265+-- performance if the stream is expensive to evaluate.
267266--
268267-- For space efficiency use the smaller stream as the second stream.
269268--
269+-- You should almost always use joinInnerMap instead of joinInner. joinInnerMap
270+-- is an order of magnitude faster. joinInner may be used when the second
271+-- stream is generated from a seed, therefore, need not be stored in memory and
272+-- the amount of memory it takes is a concern.
273+--
270274-- Space: O(n) assuming the second stream is cached in memory.
271275--
272276-- Time: O(m x n)
@@ -275,20 +279,27 @@ crossJoin s1 s2 = do
275279{-# INLINE joinInner #-}
276280joinInner ::
277281 forall (t :: (Type -> Type) -> Type -> Type) m a b.
278− (IsStream t, Monad (t m)) =>
282+ (IsStream t, Monad m) =>
279283 (a -> b -> Bool) -> t m a -> t m b -> t m (a, b)
280284joinInner eq s1 s2 = do
281− -- XXX use concatMap instead?
282− a <- s1
283− b <- s2
284− if a `eq` b
285− then return (a, b)
286− else Stream.nil
285+ -- ConcatMap works faster than bind
286+ Stream.concatMap (\a ->
287+ Stream.concatMap (\b ->
288+ if a `eq` b
289+ then Stream.fromPure (a, b)
290+ else Stream.nil
291+ ) s2
292+ ) s1
287293
288294-- If the second stream is too big it can be partitioned based on hashes and
289295-- then we can process one parition at a time.
290296--
291−-- | Like 'joinInner' but uses a hashmap for efficiency.
297+-- XXX An IntMap may be faster when the keys are Int.
298+-- XXX Use hashmap instead of map?
299+--
300+-- | Like 'joinInner' but uses a 'Map' for efficiency.
301+--
302+-- If the input streams have duplicate keys, the behavior is undefined.
292303--
293304-- For space efficiency use the smaller stream as the second stream.
294305--
@@ -297,20 +308,23 @@ joinInner eq s1 s2 = do
297308-- Time: O(m + n)
298309--
299310-- /Pre-release/
300−{-# INLINE joinInnerHash #-}
301−joinInnerHash :: (IsStream t, Monad m, Ord k) =>
311+{-# INLINE joinInnerMap #-}
312+joinInnerMap :: (IsStream t, Monad m, Ord k) =>
302313 t m (k, a) -> t m (k, b) -> t m (k, a, b)
303−joinInnerHash s1 s2 =
304− Stream.concatM $ do
305− km <- Stream.fold (Fold.classify Fold.toList) $ IsStream.adapt s2
306− return $ Stream.mapMaybe (joinAB km) s1
314+joinInnerMap s1 s2 =
315+ Stream.concatM $ do
316+ km <- kvFold $ IsStream.adapt s2
317+ pure $ Stream.mapMaybe (joinAB km) s1
318+
319+ where
307320
308− where
321+ -- XXX Generate error if a duplicate insertion is attempted?
322+ kvFold = Stream.foldl' (\kv (k, b) -> Map.insert k b kv) Map.empty
309323
310− joinAB km (k, a) =
311− case k `Map.lookup` km of
312− Just b -> Just (k, a, head b)
313− Nothing -> Nothing
324+ joinAB kvm (k, a) =
325+ case k `Map.lookup` kvm of
326+ Just b -> Just (k, a, b)
327+ Nothing -> Nothing
314328
315329-- | Like 'joinInner' but works only on sorted streams.
316330--
streamly.cabal+1−1
@@ -101,7 +101,7 @@ extra-source-files:
101101 test/Streamly/Test/Data/Array/Prim/Pinned.hs
102102 test/Streamly/Test/Data/Array/Foreign.hs
103103 test/Streamly/Test/Data/Array/Stream/Foreign.hs
104− test/Streamly/Test/Data/Parser/ParserD.hs
104+ test/Streamly/Test/Data/Parser/ParserD.hs
105105 test/Streamly/Test/FileSystem/Event.hs
106106 test/Streamly/Test/FileSystem/Event/Common.hs
107107 test/Streamly/Test/FileSystem/Event/Darwin.hs
test/Streamly/Test/Prelude/Top.hs+8−10
@@ -1,5 +1,4 @@
1−module Main (main)
2− where
1+module Main (main) where
32
43import Data.List (sort)
54import Test.QuickCheck
@@ -30,7 +29,6 @@ chooseInt = choose
3029eq :: Int -> Int -> Bool
3130eq = (==)
3231
33−
3432joinInner :: Property
3533joinInner =
3634 forAll (listOf (chooseInt (min_value, max_value))) $ \ls0 ->
@@ -47,10 +45,8 @@ joinInner =
4745 let v2 = [ (i,j) | i <- ls0, j <- ls1, i == j ]
4846 assert (v1 == v2)
4947
50−
51−
52−joinInnerHash :: Property
53−joinInnerHash =
48+joinInnerMap :: Property
49+joinInnerMap =
5450 forAll (listOf (chooseInt (min_value, max_value))) $ \ls0 ->
5551 forAll (listOf (chooseInt (min_value, max_value))) $ \ls1 ->
5652 monadicIO $ action (map (\a -> (a,a)) ls0) (map (\a -> (a,a)) ls1)
@@ -61,7 +57,7 @@ joinInnerHash =
6157 v1 <-
6258 run
6359 $ S.toList
64− $ Top.joinInnerHash (S.fromList ls0) (S.fromList ls1)
60+ $ Top.joinInnerMap (S.fromList ls0) (S.fromList ls1)
6561 let v2 = [
6662 (fst i, fst i, fst j)
6763 | i <- ls0, j <- ls1
@@ -69,8 +65,10 @@ joinInnerHash =
6965 ]
7066 assert (v1 == v2)
7167
72−
7368-------------------------------------------------------------------------------
69+-- Main
70+-------------------------------------------------------------------------------
71+
7472moduleName :: String
7573moduleName = "Prelude.Top"
7674
@@ -80,4 +78,4 @@ main = hspec $ do
8078 -- Joins
8179
8280 prop "joinInner" Main.joinInner
83− prop "joinInnerHash" Main.joinInnerHash
81+ prop "joinInnerMap" Main.joinInnerMap