Most questions I've seen regarding parallel list processing are concerned with the kind of parallelism achieved by chunking the list and processing each chunk in parallel to each other.
My question is different.
I have something simpler/more stupid in mind concerning a sequence of map
s and fold
s: what if we want to simply set up a job for the first map
which should be done in parallel to the second map
?
The structure of the computation I'm thinking of:
xs -- initial data
ys = y1 : y2 : ... : yn -- y1 = f x1, ... and so on.
-- computed in one parallel job.
zs = z1 : z2 : ... : zn -- z1 = g y1, ... and so on.
-- computed in another job (the applications of `g`), i.e., the "main" job.
Will something in the spirit of the following code work?
ys = map f xs
zs = ys `par` map g' ys
where g' y = y `pseq` g y
I'd only need to say that ys
should be evaluated with a kind of
deepSeq
instead of simply writing:
ys `par` ...
So while the main job would be busy with computing a g
, we are also forcing the premature computation of ys
in parallel.
Is there anything wrong with this approach?
The documentation and examples on par
and pseq
are a bit scarce for me to understand how this will work out. The difference of my code from what I've seen in some examples is that the values on the left side of par
and pseq
are different in my code.
Discussion
I can think of similar parallelization for other kinds of transformations (fold
, scan
, and more complex compositions).
For one thing, I'm afraid that the elements of ys
could be evaluated
twice if g
is too quick...
This should give a fixed two-times speedup with 2 cores.
And if there are more such costly transformation nodes (say, N
) in my pipeline, I'd get a fixed N
-times speedup.
As for my vertical parallelization vs theirs(1,2,etc.) horizontal (achieved with parMap
): I want to get faster streaming. In other words: I want to see the intermediate results (incremental inits zs
) faster in the first place.
CORRECTION
It seems that I didn't understand pseq
. Consider my old code from above:
zs = ys `par` map g' ys
where g' y = y `pseq` g y
and re-read the documentation for pseq
:
seq
is strict in both its arguments, so the compiler may, for example, rearrangea `seq` b
into ... . ... it can be a problem when annotating code for parallelism, because we need more control over the order of evaluation; we may want to evaluate
a
beforeb
, because we know thatb
has already been sparked in parallel withpar
.
So, in my case, y
is a part of value which I want to have sparked and forced with par
So it's like b
, and there is no need/sense to put it under a pseq
, right?
But I'm a bit afraid still whether its computation can accidentally be duplicated if we are too fast in the map....
I've also had a look at Ch.2 of Parallel and Concurrent Programming in Haskell, but they talk about rpar
and rseq
...
And they seem to imply that it is OK to do rpar/rpar
or rpar/rseq/rseq
without extra worrying for waiting for the value in the case of rpar/rpar
. Have I got something wrong?