6

I do not understand why this program using repa:

import Data.Array.Repa
import Data.Array.Repa.Algorithms.Matrix
import Data.Functor.Identity

go = runIdentity $ do
  let mat = fromListUnboxed (ix2 2 2) [1..4]
  let ins = fromListUnboxed (ix2 2 1) [1, 1]
  mmultP mat ins

is giving me the following warning:

Data.Array.Repa: Performing nested parallel computation sequentially.
  You've probably called the 'compute' or 'copy' function while another
  instance was already running. This can happen if the second version
  was suspended due to lazy evaluation. Use 'deepSeqArray' to ensure
  that each array is fully evaluated before you 'compute' the next one.

I have no nested computations, I didn't call compute or copy, and everything that I used to do the computation is inside the same monad. Is it something to do with lazy evaluation? If so, how do I make the parallel computation happen while using the Identity monad (to keep the overall computation pure) ?

For reference, replacing runIdentity with runST makes it work, although in either case the specific monad's functionality isn't being used at all.

rityzmon
  • 1,945
  • 16
  • 26
  • Looking inside the source code, I see that `mmultP` calls `computeP`, which calls `unsafePerformIO`. I'm not very experienced with this, but might there be an incompatibility between `unsafePerformIO` and the `Identity` monad (maybe `Identity`'s behaviour with laziness) ? – madjar Jun 13 '16 at 10:03
  • @madjar I have no idea. I don't see how. – rityzmon Jun 14 '16 at 07:27

1 Answers1

2

The reason for having the Monad constraint in computeP and similar parallel operations is to force sequential computation where required. This is described in [Parallel and Concurrent Programming in Haskell], in subsection Monads and computeP.

In your case, the problem seems to be caused by the internal implementation of mmultP:

mmultP  :: Monad m
        => Array U DIM2 Double 
        -> Array U DIM2 Double 
        -> m (Array U DIM2 Double)

mmultP arr brr 
 = [arr, brr] `deepSeqArrays` 
   do   trr      <- transpose2P brr
        let (Z :. h1  :. _)  = extent arr
        let (Z :. _   :. w2) = extent brr
        computeP 
         $ fromFunction (Z :. h1 :. w2)
         $ \ix   -> R.sumAllS 
                  $ R.zipWith (*)
                        (unsafeSlice arr (Any :. (row ix) :. All))
                        (unsafeSlice trr (Any :. (col ix) :. All))

It calls first transpose2P and then computeP, and transpose2P internally calls computeUnboxedP. If you use the Identity monad, there is no sequencing forced, so both these parallel computations can run in parallel, hence the nested parallelism.

If you want to keep things pure and also don't want to use ST, you can replace Identity with Eval, which is a strict version of Identity:

import Control.Parallel.Strategies
...
go = runEval $ do ...
Petr
  • 62,528
  • 13
  • 153
  • 317
  • Thank you. My confusion with this rooted in the `Identity` monad not having sequencing forced. Why? Isn't it a monad that uses `>>=` to sequence? – rityzmon Jun 18 '16 at 06:11
  • Monads describe computations, but in general not how they're evaluated. For some monads the order is enforced (`IO`, `ST`, `Eval`, ...), but for some monads not (`Reader`, `Identity`, ...). Haskell's lazy evaluation still applies, so if the order isn't forced, computations are still evaluated lazily. In particular, `Identity` is a monad that doesn't do anything at all, it only give monadic interface for pure computations. – Petr Jun 18 '16 at 07:51