8

Looking at the following example from Parallel and Concurrent Programming in Haskell:

main = do
  [n] <- getArgs
  let test = [test1,test2,test3,test4] !! (read n - 1) 
  t0 <- getCurrentTime
  r <- evaluate (runEval test)
  printTimeSince t0
  print r
  printTimeSince t0


test1 = do
  x <- rpar (fib 36)
  y <- rpar (fib 35)
  return (x,y)

The book shows its compilation:

 $ ghc -O2 rpar.hs -threaded

And then running the above test:

$ ./rpar 1 +RTS -N2
time: 0.00s
(24157817,14930352)
time: 0.83s

If I understand correctly, the Eval Monad (using rpar) results in both fib 36 and fib 35 being computed in parallel.

Does the actual work, i.e. computing the function fib ... occur when calling (runEval test)? Or perhaps evaluate ... is required? Or, finally, perhaps it gets computed when calling print r to evaluate it entirely?

It's not clear to me when the actual work gets performed for rpar.

Kevin Meredith
  • 41,036
  • 63
  • 209
  • 384
  • 1
    Probably when `print` is called? since your first print shows no time elapsed for the line `r <- evaluate (runEval test)` and 0.83s for `print r`. – Alec Jun 18 '15 at 15:28
  • When you use `evaluate`, you evaluate the argument to [weak head normal form](https://wiki.haskell.org/Weak_head_normal_form). The expressing `(x,y)` is already in WHNF, so there is nothing to evaluate! Since you haven't demanded the values of `x` and `y` themselves yet, they won't be evaluated. Then the call to `print` forces the evaluation of `x` and `y` to normal form, which in turn sparks the parallel computation you expect. – user2407038 Jun 18 '15 at 16:33
  • @user2407038 - thanks. Care to post for credit? I think it deserves an answer post, not a comment alone. – Kevin Meredith Jun 18 '15 at 17:45

1 Answers1

5

Here's my guess, but I can't seem to replicate this on my laptop, too many imports I'd have to get from cabal.

test1 = do
  x <- rpar (fib 36)
  y <- rpar (fib 35)
  return (x,y)

In this, you spark the evaluation of (fib 36) and (fib 35) in parallel, but you don't wait for them - you just return (x,y) immediately, while x and y are still evaluating. Then, we you get to print r, you are forced to wait until x and y finish evaluating.

In theory, the following code should force test1 to wait until x and y have finished evaluating before returning them.

test1 = do
  x <- rpar (fib 36)
  y <- rpar (fib 35)
  rseq x
  rseq y
  return (x,y)

Then, running this should give you approximately

$ ./rpar 1 +RTS -N2
time: 0.83s
(24157817,14930352)
time: 0.83s

hopefully...

EDIT

Finally got back to my machine, replicated the condition, and my suggested code gives the expected result. However, the OP raises another good question: if evaluate only evaluates to the WHNF, why does it even end up doing work before print is called?

The answer is in the monad definition of Control.Parallel.Strategies - in other words, it isn't evaluate that pushes the evaluation of x and y, but runEval. The Eval monad is strict in the first argument: in x >>= f it will evaluate x (please check out this question before continuing). Then, de-sugaring test1 gives:

test1 = (rpar (fib 36)) >>= (\x -> 
        (rpar (fib 35)) >>= (\y ->
        (rseq x)        >>= (\_ ->
        (rseq y)        >>= (\_ ->
        return (x,y)))))

Then, since rpar only "sparks" evaluation, it uses par (which begins the evaluation of the first argument but immediately returns the second) and immediately returns Done, however, rseq (like seq, but strict only in the first argument) does not return Done until its argument is actually evaluated (to WHNF). Hence, without the rseq calls, you have know that x and y have begun to be evaluated but no assurance that they have finished, but with those calls, you know both x and y are also evaluated before return is called on them.

Community
  • 1
  • 1
Alec
  • 31,829
  • 7
  • 67
  • 114
  • From the above book's link: `The evaluate function is like a seq in the IO monad: it evaluates its argument to weak head normal form and then returns it:`. I interpret that to mean that, when calling `evaluate (runEval test)`, it gets evaluated to weak head normal form. I don't understand how that means that `rpar (fib 35)` and `rpar (fib 36)` are running in the background as a result of the call to `evaluate`. – Kevin Meredith Jun 20 '15 at 02:38
  • @KevinMeredith Let me know if the edit answers your question fully. – Alec Jun 20 '15 at 04:00