From 052c2a69b57b41eeb47ba79a1f526b48b5e87cdf Mon Sep 17 00:00:00 2001 From: David Feuer Date: Thu, 21 Jun 2018 12:41:21 -0400 Subject: [PATCH 1/3] Chunk better `parListChunk` previously split a list up into chunks, applied the given strategy to each chunk, and then put them all together again. This led to two extra copies of the list. We get very little benefit from actually splitting the list, because the parallel computations need to traverse their part anyway; we can instead just hand off the whole list and let them count out their chunk. We count each chunk twice, but that shouldn't cost enough to matter. Now that `Eval` has a `MonadFix` instance, we can avoid actually having to put together lists at the end; instead, we pass each parallel computation the (as-yet-uncomputed) result of calculating the rest of the list. --- Control/Parallel/Strategies.hs | 36 ++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/Control/Parallel/Strategies.hs b/Control/Parallel/Strategies.hs index 20bf8b0..2a2c042 100644 --- a/Control/Parallel/Strategies.hs +++ b/Control/Parallel/Strategies.hs @@ -1,5 +1,6 @@ {-# LANGUAGE BangPatterns, CPP, MagicHash, UnboxedTuples #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE RecursiveDo #-} ----------------------------------------------------------------------------- -- | -- Module : Control.Parallel.Strategies @@ -599,13 +600,36 @@ parListNth n strat = evalListNth n (rparWith strat) -- 'parList' -- parListChunk :: Int -> Strategy a -> Strategy [a] -parListChunk n strat xs - | n <= 1 = parList strat xs - | otherwise = concat `fmap` parList (evalList strat) (chunk n xs) +parListChunk = parListChunk' -chunk :: Int -> [a] -> [[a]] -chunk _ [] = [] -chunk n xs = as : chunk n bs where (as,bs) = splitAt n xs +parListChunk' :: Int -> (a -> Eval b) -> [a] -> Eval [b] +parListChunk' n strat + | n <= 1 = traverse strat + +-- parListChunk n strat xs = +-- concat `fmap` 'parList' ('evalList' strat) (chunk n xs) +-- but we avoid building intermediate lists. +parListChunk' n0 strat = go n0 + where + go !_n [] = pure [] + go n as = mdo + -- Calculate the first chunk in parallel, passing it the result + -- of calculating the rest + bs <- rpar $ runEval $ evalChunk strat more n as + + -- Calculate the rest + more <- go n (drop n as) + return bs + +-- | @evalChunk strat end n as@ uses @strat@ to evaluate the first @n@ +-- elements of @as@ (ignoring the rest) and appends @end@ to the result. +evalChunk :: (a -> Eval b) -> [b] -> Int -> [a] -> Eval [b] +evalChunk strat = \end -> + let + go !_n [] = pure end + go 0 _ = pure end + go n (a:as) = (:) <$> strat a <*> go (n - 1) as + in go -- -------------------------------------------------------------------------- -- Convenience From f95bd434aa23350535ff065f2ccb35633e9f3c74 Mon Sep 17 00:00:00 2001 From: konsumlamm <44230978+konsumlamm@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:50:46 +0200 Subject: [PATCH 2/3] Simplify CI (#76) From 6bf9f76f4b6a0a48b2e830d7cc702c92fd684dd6 Mon Sep 17 00:00:00 2001 From: konsumlamm Date: Tue, 20 May 2025 13:15:59 +0200 Subject: [PATCH 3/3] `parListChunk` improvements --- Control/Parallel/Strategies.hs | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/Control/Parallel/Strategies.hs b/Control/Parallel/Strategies.hs index 2a2c042..419110c 100644 --- a/Control/Parallel/Strategies.hs +++ b/Control/Parallel/Strategies.hs @@ -593,37 +593,29 @@ parListNth n strat = evalListNth n (rparWith strat) -- | Divides a list into chunks, and applies the strategy -- @'evalList' strat@ to each chunk in parallel. -- --- It is expected that this function will be replaced by a more --- generic clustering infrastructure in the future. --- -- If the chunk size is 1 or less, 'parListChunk' is equivalent to -- 'parList' -- +-- This function may be replaced by a more +-- generic clustering infrastructure in the future. parListChunk :: Int -> Strategy a -> Strategy [a] -parListChunk = parListChunk' - -parListChunk' :: Int -> (a -> Eval b) -> [a] -> Eval [b] -parListChunk' n strat - | n <= 1 = traverse strat - --- parListChunk n strat xs = --- concat `fmap` 'parList' ('evalList' strat) (chunk n xs) --- but we avoid building intermediate lists. -parListChunk' n0 strat = go n0 +parListChunk n strat + | n <= 1 = parList strat + | otherwise = go where - go !_n [] = pure [] - go n as = mdo + go [] = pure [] + go as = mdo -- Calculate the first chunk in parallel, passing it the result -- of calculating the rest bs <- rpar $ runEval $ evalChunk strat more n as -- Calculate the rest - more <- go n (drop n as) + more <- go (drop n as) return bs -- | @evalChunk strat end n as@ uses @strat@ to evaluate the first @n@ -- elements of @as@ (ignoring the rest) and appends @end@ to the result. -evalChunk :: (a -> Eval b) -> [b] -> Int -> [a] -> Eval [b] +evalChunk :: Strategy a -> [a] -> Int -> Strategy [a] evalChunk strat = \end -> let go !_n [] = pure end