
Hello, everyone. I have just written a tutorial (sort of) on Haskell concurrency, specifically on the derivation of a function mapP, a parallel version of map. I wrote this because I am fairly new to Haskell, and I didn't realize how easy concurrent code is until I wrote this, and because I could not find a tutorial describing a parallel implementation of map. If anybody is curious to see this, the page is http://zwell.net/content/haskell.html#mapP . I would appreciate any criticism, too. I hope somebody finds this helpful. Dan Zwell

Dan Zwell wrote:
Hello, everyone. I have just written a tutorial (sort of) on Haskell concurrency, ^^^^^^^^^^^ specifically on the derivation of a function mapP, a parallel version of map. I wrote this because I am fairly new to Haskell, and I didn't realize how easy concurrent code is until I wrote this, and because I could not find a tutorial describing a parallel implementation of map.
If anybody is curious to see this, the page is http://zwell.net/content/haskell.html#mapP . I would appreciate any criticism, too. I hope somebody finds this helpful.
I have a criticism ;-) but it does not concern your page but above highlighted part of your message: because you have /not/ written about concurrency (and, gladly, I noticed that your page doesn't even mention the word). Haskell's support for parallelism (about which you write) is designed to /avoid/ (all the well-known issues of) concurrency. A tutorial about concurrency in Haskell would have to mention IO, explicit threads, shared mutable variables, etc... (which /do/ exist, as I assume you know). Cheers Ben

On Sun, Feb 24, 2008 at 1:45 AM, Dan Zwell
If anybody is curious to see this, the page is http://zwell.net/content/haskell.html#mapP . I would appreciate any criticism, too. I hope somebody finds this helpful.
Have you seen parBuffer? I'd also recommend looking at its source. http://haskell.org/ghc/docs/latest/html/libraries/parallel/Control-Parallel-... HTH, -- Felipe.

Felipe Lessa wrote:
On Sun, Feb 24, 2008 at 1:45 AM, Dan Zwell
wrote: If anybody is curious to see this, the page is http://zwell.net/content/haskell.html#mapP . I would appreciate any criticism, too. I hope somebody finds this helpful.
Have you seen parBuffer? I'd also recommend looking at its source.
Indeed, since this is yet another case of a base library function where the really useful documentation is hidden in a regular comment in the sources (in this case the documentation for the commented-out fringeList function), while the haddock-exposed docs explain nothing. Cheers Ben

Felipe Lessa wrote:
Have you seen parBuffer? I'd also recommend looking at its source.
I wonder if it would be possible to make a variant of parBuffer so that the following evaluates to 1:
take 1 $ parBuffer 10 r0 (1:2:3:undefined) *** Exception: Prelude.undefined
Maybe we should use a more complex intermediate structure as data ExcList a = Nil | Cons a (ExcList a) | Deferred Exception and catch the exception through some unsafe stuff? Would that be a good idea? Zun.

According to Lennart Augustsson (http://haskell.org/pipermail/haskell-cafe/2007-July/029603.html) you can have uninterruptible threads in ghc. If a thread never allocates it will never be preempted. So I wonder if what you are proposing really is unsafe because by the time (take 1) is even invoked you might not regain control of the CPU being held hostage by parBuffer trying to evaluate undefined. After all, undefined can encompass non-halting calculations as well as abortive ones. In other words, undefined is not an exception (despite the error message). Converting it to a catchable exception is tantamount to pretending that the threading model is fully preemptive. Dan Roberto Zunino wrote:
Felipe Lessa wrote:
Have you seen parBuffer? I'd also recommend looking at its source.
I wonder if it would be possible to make a variant of parBuffer so that the following evaluates to 1:
take 1 $ parBuffer 10 r0 (1:2:3:undefined) *** Exception: Prelude.undefined
Maybe we should use a more complex intermediate structure as
data ExcList a = Nil | Cons a (ExcList a) | Deferred Exception
and catch the exception through some unsafe stuff? Would that be a good idea?
Zun. _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

Dan Weston wrote:
According to Lennart Augustsson (http://haskell.org/pipermail/haskell-cafe/2007-July/029603.html) you can have uninterruptible threads in ghc. If a thread never allocates it will never be preempted.
I am aware of that. I think I heard GHC devs acknowledge that it is indeed a bug, though. Of course, it occurs only in small examples, so it is not in the urgent queue of bugs to be fixed.
So I wonder if what you are proposing really is unsafe because by the time (take 1) is even invoked you might not regain control of the CPU being held hostage by parBuffer trying to evaluate undefined. After all, undefined can encompass non-halting calculations as well as abortive ones.
In other words, undefined is not an exception (despite the error message). Converting it to a catchable exception is tantamount to pretending that the threading model is fully preemptive.
As it should be, IMO. Note that other examples do show that exceptions raised in sparked threads are ignored: undefined `par` x == x even if x does perform allocations. Of course, if that undefined is indeed demanded in the main thread, it will raise an exception. Note that if you take my previous example and replace undefined with a plain non-termination, say a well-behaved "allocating" one, I still think that take 1 $ parBuffer 10 r0 (1:2:3:nonTerminating) still should terminate, evaluating to 1. The problem, I think, lies in parBuffer being more strict than needed. The comments in the code claim parBuffer n r0 == id when it is actually stricter than id, as I shown. This also means there's a loss of parallelism: in parBuffer n strat . map f . parBuffer m strat the second parBuffer will not output its results unless after m of them are "ready", so the first parBuffer can not start applying f to them. Zun.

Felipe Lessa wrote:
On Sun, Feb 24, 2008 at 1:45 AM, Dan Zwell
wrote: If anybody is curious to see this, the page is http://zwell.net/content/haskell.html#mapP . I would appreciate any criticism, too. I hope somebody finds this helpful.
Have you seen parBuffer? I'd also recommend looking at its source. http://haskell.org/ghc/docs/latest/html/libraries/parallel/Control-Parallel-...
Thanks. parBuffer looks more elegant than what I wrote, though it seems that it performs slightly slower--I assume that pattern matching (x:[]) versus (x:y:[]) (etc.) is faster than recursing and keeping a count of the number of elements left to spark. I tested this with mapP f xs = parBuffer 2 rwhnf $ map f xs (though I had to change the test from doing a few slow operations to doing many fast operations, to see any difference.) Clearly using parBuffer would be a win on machines with lots of CPUs, but is there any reason that I would want to use it instead of the mapP I've already defined? Dan

On Tue, Feb 26, 2008 at 12:49 AM, Dan Zwell
Clearly using parBuffer would be a win on machines with lots of CPUs, but is there any reason that I would want to use it instead of the mapP I've already defined?
You certainly don't want the end user to modify a lot of handwritten functions just to use your program on his brand new processor. Actually, you don't want even to force him to recompile your program! (think of distributed binaries) Besides, most programs go beyond simple list processing. Maybe using a fixed constant (not related to the number of processors) may be a sensible default for part of a program that you don't know how many lists are being processed in parallel because they are part of a bigger computation outside its scope. But I don't have much experience here, so I'll let the par-gurus from the mailing list to explain better =). -- Felipe.
participants (5)
-
Ben Franksen
-
Dan Weston
-
Dan Zwell
-
Felipe Lessa
-
Roberto Zunino