# Why is this parallel evaluation with Dispatch[] so slow?

Can you efficiently parallelize this? The parallel versions are much slower than the sequential version, and I'm not sure why. Does SetSharedVariable allow simultaneous reads for different kernels? It appears that it doesn't even though the documentation says you should use CriticalSection to make separate reads and writes atomic and thread-safe.

dispatch = Dispatch@Table[i -> RandomReal[1, 500], {i, 20000}];

Map[Select[# /. dispatch, # < .1 &] &,
RandomInteger[{1, 20000}, 10000]] (* < 3 secs *)

ParallelMap[Select[# /. dispatch, # < .1 &] &,
RandomInteger[{1, 20000}, 10000]] (* 50 secs *)

SetSharedVariable[dispatch]; ParallelMap[
Select[# /. dispatch, # < .1 &] &, RandomInteger[{1, 20000}, 10000],
DistributedContexts -> None] (* longer than 2 minutes *)

-
SetSharedVariable forces that variable to be evaluated on the main kernel (not the parallel kernels). This will effectively make your code not run in parallel. –  Szabolcs May 15 '13 at 20:55
So the slowdown for the second one is from copying definitions between kernels? –  Michael Hale May 15 '13 at 20:56
Moving data (not definitions) and waiting for the main kernel to finish. There's also a bug in the parallel tools which temporarily unpacks packed arrays sent back to the main kernel, using up a lot of memory and causing a considerable slowdown. See here: mathematica.stackexchange.com/q/2886/12 Generally, parallelization is valuable if your calculation take a longer time, if it's not too fine grained (minimize communication between kernels), and unfortunately: if the data sent back is not too large. –  Szabolcs May 15 '13 at 21:03
No problem. I can stick with the sequential version for now. Thanks for the information. –  Michael Hale May 15 '13 at 21:06
I think this slowness is a bug (see my update in the answer). I re-titled your question because the problem is not (only) caused by SetSharedVariable. –  Szabolcs May 15 '13 at 22:33
show 3 more comments

## 3 Answers

To answer your actual question, SetSharedVariable does not allow simultaneous reads. It forces the variable to be evaluated on the main kernel, effectively disabling the parallelization.

A more interesting question for me is: why is ParallelMap so slow when not using SetSharedVariable? This observation is not an answer but it's too long for a comment.

Consider this simplified example:

n = 1500;
data = RandomInteger[{1, n}, 10000];

dp = Dispatch@Table[i -> RandomReal[1, 1000], {i, n}];

Map[Select[# /. dp, # < 0.1 &] &, data]; // AbsoluteTiming
ParallelMap[Select[# /. dp, # < 0.1 &] &, data]; // AbsoluteTiming


On my machine, the Map version takes about 5 seconds, regardless of the size of n (at least for n between 100 and 10000). However, the ParallelMap version depends strongly on the value of n: for n=100 it's much faster than Map, but for n=2000 it already takes a bit longer. If I remove the Dispatch@, it will be faster.

ParallelEvaluate[dp]; is not slow, which suggests that it is not transferring dp to the parallel kernels that takes time.

### Update: evaluating Rule on parallel kernels is very slow

Finally I managed to come up with a smaller and more enlightening test case for this slowdown. In a fresh kernel, evaluate:

LaunchKernels[]

data = Table[Rule[1, 1], {1000}];

AbsoluteTiming[Table[data, {1000}];]     (* very fast *)
(* ==> {0.001288, Null} *)

DistributeDefinitions[data] (* ParallelEvaluate doesn't auto-distribute *)

ParallelEvaluate[AbsoluteTiming[Table[data, {1000}];]]
(* ==>
{{0.939300, Null}, {0.947640, Null}, {0.941881, Null}, {0.931997, Null},
{0.925231, Null}, {0.930565, Null}, {0.930977, Null}, {0.931430, Null}} *)


This is very slow, and it is the parallel kernels that take so long to evaluate it!

Now let's change data to

data = Table[rule[1, 1], {1000}];

DistributeDefinitions[data]

ParallelEvaluate[AbsoluteTiming[Table[data, {1000}];]]
(* ==>
{{0.000197, Null}, {0.000212, Null}, {0.000347, Null}, {0.000192, Null},
{0.000220, Null}, {0.000327, Null}, {0.000331, Null}, {0.000197, Null}} *)


This is now very fast. So the culprit is Rule. But why? Is Rule overloaded in the parallel kernels? ParallelEvaluate[Information[Rule]] reveals nothing special.

Does anyone have any ideas what might be going on here?

Update on 2014-03-04:

I received a reply about this from WRI support and they pointed out that the issue is mentioned in the documentation. It's the third entry under "Possible Issues" at DistributeDefinitions.

The example there describes the same problem with InterpolatingFunction objects. Quoting the relevant part of the documentation, without repeating the example code:

Certain objects with an internal state may not work efficiently when distributed. ... Certain objects with an internal state may not work efficiently when distributed. ... Alternatively, reevaluate the data on all subkernels.

For the example above the workaround is as simple as

ParallelEvaluate[data = data;]


This is very simple to @Michael's workaround in the other answer, but it's not even necessary to recreate the expressions form scratch. It's sufficient to just re-assign the variable to itself (and re-evaluate it in the process).

-
I reported this to support yesterday. –  Szabolcs May 16 '13 at 15:59
I'm wondering what the support said about this? –  xslittlegrass Sep 3 '13 at 1:35
@xslittlegrass They said they filed a report, I haven't heard anything since. It hasn't affected me directly so I have not pursued it. I wouldn't normally report problems that don't hinder me personally but in this case I spent so much time debugging the OP's problem (why do I do this?!) that I couldn't let it go ... –  Szabolcs Sep 3 '13 at 3:55
add comment

Here is a way in which ParallelMap works three times faster (V9.0.1, Mac OS, Intel i7, Quad core, 8 virtual cores). The trick is to evaluate dispatch = Dispatch@rules on each kernel.

I don't really have an explanation, other than the guess that the dispatch table resides wholly in each kernel's memory and the observation that when rules are distributed, they take use half the memory in the parallel kernels than in the main kernel. When the rules are evaluated in the parallel kernels, even by ParallelEvaluate[ByteCount[rules]], the memory size of the kernels swells to match the main kernel. I assume that the rest of the definition of rules is transferred. But when the calculation is over, the kernel memory usage goes back down to what it was. So while the definition of rules is registered with the parallel kernels, it appears that the data itself is not completely copied.

rules = Table[i -> RandomReal[1, 500], {i, 20000}];

dispatch0 = Dispatch @ rules;
Map[Select[# /. dispatch0, # < .1 &] &,
RandomInteger[{1, 20000}, 10000]]; // AbsoluteTiming

DistributeDefinitions[rules];
ParallelEvaluate[dispatch = Dispatch@rules];
ParallelMap[Select[# /. dispatch, # < .1 &] &,
RandomInteger[{1, 20000}, 10000]]; // AbsoluteTiming

(* {2.851229, Null} *)

(* {0.955070, Null} *)


Note that distributing the definitions and evaluate Dispatch takes some seconds (almost five), so that overall, the nonparallel calculation is still faster.

-
Useful tip. My original motivation involved a larger dispatch table and a longer operation using the dispatch table. Paging issues might have been unavoidable when making multiple copies of a dispatch table as large as my original, but I'll keep this in mind for situations with large but not huge memory requirements and long evaluation times. –  Michael Hale Nov 19 '13 at 19:04
add comment

This non-answer (addressing Szabolcs' observation) won't fit in a comment.

The following makes me suspect that the problem has something to do with how the parallel kernels are representing data internally:

LaunchKernels[];

datatest = Table[rule[x, x], {2}];
data = Table[Rule[x, x], {2}];

Trace[datatest]
(* ==> {datatest,{rule[x,x],rule[x,x]}}*)
Trace[data]
(* ==> {data,{x->x,x->x}}*)

DistributeDefinitions[datatest, data];

ParallelEvaluate[foo = datatest /. rule -> Rule];

ParallelEvaluate[Trace[datatest]]
(* ==>{{datatest,{rule[x,x],rule[x,x]}},{datatest,{rule[x,x],rule[x,x]}},
{datatest,{rule[x,x],rule[x,x]}},{datatest,{rule[x,x],rule[x,x]}},
{datatest,{rule[x,x],rule[x,x]}},{datatest,{rule[x,x],rule[x,x]}},
{datatest,{rule[x,x],rule[x,x]}},{datatest,{rule[x,x],rule[x,x]}}} *)

ParallelEvaluate[Trace[data]]
(* ==> {{data,{x->x,x->x},{x->x,x->x},{x->x,x->x},{x->x,x->x}},
{data,{x->x,x->x},{x->x,x->x},{x->x,x->x},{x->x,x->x}},
{data,{x->x,x->x},{x->x,x->x},{x->x,x->x},{x->x,x->x}},
{data,{x->x,x->x},{x->x,x->x},{x->x,x->x},{x->x,x->x}},
{data,{x->x,x->x},{x->x,x->x},{x->x,x->x},{x->x,x->x}},
{data,{x->x,x->x},{x->x,x->x},{x->x,x->x},{x->x,x->x}},
{data,{x->x,x->x},{x->x,x->x},{x->x,x->x},{x->x,x->x}},
{data,{x->x,x->x},{x->x,x->x},{x->x,x->x},{x->x,x->x}}} *)

ParallelEvaluate[Trace[foo]]
(* {{foo,{x->x,x->x}},{foo,{x->x,x->x}},{foo,{x->x,x->x}},
{foo,{x->x,x->x}},{foo,{x->x,x->x}},{foo,{x->x,x->x}},
{foo,{x->x,x->x}},{foo,{x->x,x->x}}} *)


In the parallel kernels, data and foo are Equal, satisfy SameQ, and have, as near as I can tell, the equivalent LanguageExtendedFullDefinition`, but one can see from the traces that the evaluation paths are different.

-
Btw, if you use double backticks around your code snippent, you can use single backticks inside –  Rojo May 16 '13 at 16:11
@Rojo - thanks. –  Tobias Hagge May 16 '13 at 19:26
add comment