on master help sign in
Optimize and rename joinInnerHash to joinInnerMap
66fe3d1Harendra Kumar
benchmark/Streamly/Benchmark/Prelude/Serial/NestedStream.hs+5−5
@@ -428,11 +428,11 @@ joinInner :: Int -> Int -> Int -> IO ()
428joinInner val1 val2 _ =
428joinInner val1 val2 _ =
429 S.drain $ Internal.joinInner (==) (mkStreamLen val1) $ mkStreamLen val2
429 S.drain $ Internal.joinInner (==) (mkStreamLen val1) $ mkStreamLen val2
430
430
431−{-# INLINE joinInnerHash #-}
431+{-# INLINE joinInnerMap #-}
432−joinInnerHash :: Int -> Int -> Int -> IO ()
432+joinInnerMap :: Int -> Int -> Int -> IO ()
433−joinInnerHash val1 val2 _ =
433+joinInnerMap val1 val2 _ =
434 S.drain $
434 S.drain $
435− Internal.joinInnerHash
435+ Internal.joinInnerMap
436 (fmap toKvMap (mkStreamLen val1))
436 (fmap toKvMap (mkStreamLen val1))
437 (fmap toKvMap (mkStreamLen val2))
437 (fmap toKvMap (mkStreamLen val2))
438
438
@@ -441,7 +441,7 @@ o_n_heap_buffering value =
441 [ bgroup "buffered"
441 [ bgroup "buffered"
442 [
442 [
443 benchIOSrc1 "joinInner" (joinInner sqrtVal sqrtVal)
443 benchIOSrc1 "joinInner" (joinInner sqrtVal sqrtVal)
444− , benchIOSrc1 "joinInnerHash" (joinInnerHash sqrtVal sqrtVal)
444+ , benchIOSrc1 "joinInnerMap" (joinInnerMap sqrtVal sqrtVal)
445 ]
445 ]
446 ]
446 ]
447
447
src/Streamly/Internal/Data/Stream/IsStream/Top.hs+38−24
@@ -37,8 +37,8 @@ module Streamly.Internal.Data.Stream.IsStream.Top
37 -- ** Join operations
37 -- ** Join operations
38 , crossJoin
38 , crossJoin
39 , joinInner
39 , joinInner
40+ , joinInnerMap
40 , joinInnerMerge
41 , joinInnerMerge
41− , joinInnerHash
42 , leftJoin
42 , leftJoin
43 , mergeLeftJoin
43 , mergeLeftJoin
44 , hashLeftJoin
44 , hashLeftJoin
@@ -80,7 +80,6 @@ import qualified Streamly.Internal.Data.Stream.IsStream.Reduce as Stream
80import qualified Streamly.Internal.Data.Stream.IsStream.Transform as Stream
80import qualified Streamly.Internal.Data.Stream.IsStream.Transform as Stream
81import qualified Streamly.Internal.Data.Stream.IsStream.Type as IsStream
81import qualified Streamly.Internal.Data.Stream.IsStream.Type as IsStream
82
82
83−
84import Prelude hiding (filter, zipWith, concatMap, concat)
83import Prelude hiding (filter, zipWith, concatMap, concat)
85
84
86-- $setup
85-- $setup
@@ -261,12 +260,17 @@ crossJoin s1 s2 = do
261-- are equal by the given equality pedicate then return the tuple (a, b).
260-- are equal by the given equality pedicate then return the tuple (a, b).
262--
261--
263-- The second stream is evaluated multiple times. If the stream is a
262-- 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'
263+-- consume-once stream then the caller should cache it (e.g. in a
265−-- before calling this function. Caching may also improve performance if the
264+-- 'Data.Array.Array') before calling this function. Caching may also improve
266−-- stream is expensive to evaluate.
265+-- performance if the stream is expensive to evaluate.
267--
266--
268-- For space efficiency use the smaller stream as the second stream.
267-- For space efficiency use the smaller stream as the second stream.
269--
268--
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+--
270-- Space: O(n) assuming the second stream is cached in memory.
274-- Space: O(n) assuming the second stream is cached in memory.
271--
275--
272-- Time: O(m x n)
276-- Time: O(m x n)
@@ -275,20 +279,27 @@ crossJoin s1 s2 = do
275{-# INLINE joinInner #-}
279{-# INLINE joinInner #-}
276joinInner ::
280joinInner ::
277 forall (t :: (Type -> Type) -> Type -> Type) m a b.
281 forall (t :: (Type -> Type) -> Type -> Type) m a b.
278− (IsStream t, Monad (t m)) =>
282+ (IsStream t, Monad m) =>
279 (a -> b -> Bool) -> t m a -> t m b -> t m (a, b)
283 (a -> b -> Bool) -> t m a -> t m b -> t m (a, b)
280joinInner eq s1 s2 = do
284joinInner eq s1 s2 = do
281− -- XXX use concatMap instead?
285+ -- ConcatMap works faster than bind
282− a <- s1
286+ Stream.concatMap (\a ->
283− b <- s2
287+ Stream.concatMap (\b ->
284− if a `eq` b
288+ if a `eq` b
285− then return (a, b)
289+ then Stream.fromPure (a, b)
286− else Stream.nil
290+ else Stream.nil
291+ ) s2
292+ ) s1
287
293
288-- If the second stream is too big it can be partitioned based on hashes and
294-- If the second stream is too big it can be partitioned based on hashes and
289-- then we can process one parition at a time.
295-- then we can process one parition at a time.
290--
296--
297+-- XXX An IntMap may be faster when the keys are Int.
298+-- XXX Use hashmap instead of map?
299+--
291−-- | Like 'joinInner' but uses a hashmap for efficiency.
300+-- | Like 'joinInner' but uses a 'Map' for efficiency.
301+--
302+-- If the input streams have duplicate keys, the behavior is undefined.
292--
303--
293-- For space efficiency use the smaller stream as the second stream.
304-- For space efficiency use the smaller stream as the second stream.
294--
305--
@@ -297,20 +308,23 @@ joinInner eq s1 s2 = do
297-- Time: O(m + n)
308-- Time: O(m + n)
298--
309--
299-- /Pre-release/
310-- /Pre-release/
300−{-# INLINE joinInnerHash #-}
311+{-# INLINE joinInnerMap #-}
301−joinInnerHash :: (IsStream t, Monad m, Ord k) =>
312+joinInnerMap :: (IsStream t, Monad m, Ord k) =>
302 t m (k, a) -> t m (k, b) -> t m (k, a, b)
313 t m (k, a) -> t m (k, b) -> t m (k, a, b)
303−joinInnerHash s1 s2 =
314+joinInnerMap s1 s2 =
304− Stream.concatM $ do
315+ Stream.concatM $ do
305− km <- Stream.fold (Fold.classify Fold.toList) $ IsStream.adapt s2
316+ km <- kvFold $ IsStream.adapt s2
306− return $ Stream.mapMaybe (joinAB km) s1
317+ pure $ Stream.mapMaybe (joinAB km) s1
318+
319+ where
307
320
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
309
323
310− joinAB km (k, a) =
324+ joinAB kvm (k, a) =
311− case k `Map.lookup` km of
325+ case k `Map.lookup` kvm of
312− Just b -> Just (k, a, head b)
326+ Just b -> Just (k, a, b)
313− Nothing -> Nothing
327+ Nothing -> Nothing
314
328
315-- | Like 'joinInner' but works only on sorted streams.
329-- | Like 'joinInner' but works only on sorted streams.
316--
330--
streamly.cabal+1−1
@@ -101,7 +101,7 @@ extra-source-files:
101 test/Streamly/Test/Data/Array/Prim/Pinned.hs
101 test/Streamly/Test/Data/Array/Prim/Pinned.hs
102 test/Streamly/Test/Data/Array/Foreign.hs
102 test/Streamly/Test/Data/Array/Foreign.hs
103 test/Streamly/Test/Data/Array/Stream/Foreign.hs
103 test/Streamly/Test/Data/Array/Stream/Foreign.hs
104− test/Streamly/Test/Data/Parser/ParserD.hs
104+ test/Streamly/Test/Data/Parser/ParserD.hs
105 test/Streamly/Test/FileSystem/Event.hs
105 test/Streamly/Test/FileSystem/Event.hs
106 test/Streamly/Test/FileSystem/Event/Common.hs
106 test/Streamly/Test/FileSystem/Event/Common.hs
107 test/Streamly/Test/FileSystem/Event/Darwin.hs
107 test/Streamly/Test/FileSystem/Event/Darwin.hs
test/Streamly/Test/Prelude/Top.hs+8−10
@@ -1,5 +1,4 @@
1−module Main (main)
1+module Main (main) where
2− where
3
2
4import Data.List (sort)
3import Data.List (sort)
5import Test.QuickCheck
4import Test.QuickCheck
@@ -30,7 +29,6 @@ chooseInt = choose
30eq :: Int -> Int -> Bool
29eq :: Int -> Int -> Bool
31eq = (==)
30eq = (==)
32
31
33−
34joinInner :: Property
32joinInner :: Property
35joinInner =
33joinInner =
36 forAll (listOf (chooseInt (min_value, max_value))) $ \ls0 ->
34 forAll (listOf (chooseInt (min_value, max_value))) $ \ls0 ->
@@ -47,10 +45,8 @@ joinInner =
47 let v2 = [ (i,j) | i <- ls0, j <- ls1, i == j ]
45 let v2 = [ (i,j) | i <- ls0, j <- ls1, i == j ]
48 assert (v1 == v2)
46 assert (v1 == v2)
49
47
50−
48+joinInnerMap :: Property
51−
49+joinInnerMap =
52−joinInnerHash :: Property
53−joinInnerHash =
54 forAll (listOf (chooseInt (min_value, max_value))) $ \ls0 ->
50 forAll (listOf (chooseInt (min_value, max_value))) $ \ls0 ->
55 forAll (listOf (chooseInt (min_value, max_value))) $ \ls1 ->
51 forAll (listOf (chooseInt (min_value, max_value))) $ \ls1 ->
56 monadicIO $ action (map (\a -> (a,a)) ls0) (map (\a -> (a,a)) ls1)
52 monadicIO $ action (map (\a -> (a,a)) ls0) (map (\a -> (a,a)) ls1)
@@ -61,7 +57,7 @@ joinInnerHash =
61 v1 <-
57 v1 <-
62 run
58 run
63 $ S.toList
59 $ S.toList
64− $ Top.joinInnerHash (S.fromList ls0) (S.fromList ls1)
60+ $ Top.joinInnerMap (S.fromList ls0) (S.fromList ls1)
65 let v2 = [
61 let v2 = [
66 (fst i, fst i, fst j)
62 (fst i, fst i, fst j)
67 | i <- ls0, j <- ls1
63 | i <- ls0, j <- ls1
@@ -69,8 +65,10 @@ joinInnerHash =
69 ]
65 ]
70 assert (v1 == v2)
66 assert (v1 == v2)
71
67
72−
73-------------------------------------------------------------------------------
68-------------------------------------------------------------------------------
69+-- Main
70+-------------------------------------------------------------------------------
71+
74moduleName :: String
72moduleName :: String
75moduleName = "Prelude.Top"
73moduleName = "Prelude.Top"
76
74
@@ -80,4 +78,4 @@ main = hspec $ do
80 -- Joins
78 -- Joins
81
79
82 prop "joinInner" Main.joinInner
80 prop "joinInner" Main.joinInner
83− prop "joinInnerHash" Main.joinInnerHash
81+ prop "joinInnerMap" Main.joinInnerMap