[System: V13.0.0, Mac M1 Max, 10 cores]
You can observe a rough analysis with the menu command Evaluation > Parallel Kernel Status.... We can do a bit better by timing each integral. It will be seen that the integrals take relatively long time, and the overhead of parallelization of just about any parallel setup won't be very significant. The real overhead cost here is the initialization of the Integrate
code on each kernel. (If I run @ShinKim's SharedVariable
method twice, the second run takes less than a second, unless I permute ind
, in which case, it runs only a second or two faster than the original run. This shows, I think, that in addition to whatever initialization there is, results or parts of them are cached.) Since the longer integrals take 3-5s, a kernel that happens to get two such integrals might take 10s to get its job done, though in fact, as luck would have it, it's more like 8-9s. If you have only 4 kernels, then some are going to get 4 integrals, maybe four long ones.
Now, the interesting part is the cacheing. I don't know what is cached (or perhaps just autoloaded and initialized). If we do the same analysis on the unparallelized integrations, we see only a couple of long integrals and the rest are faster. Thus, in parallelizing, we lose the benefit of this cacheing (if that is what it is).
Before each run:
Quit[]
LaunchKernels[];
lmax = 4; (* reduced to save time :) *)
expr = ((1 + f0)*q*Cos[ϕ]*Sin[θ])/(Sqrt[f0]*r0) -
((Cos[ϕ]*Sin[θ] + f0*Cos[ϕ]*Sin[θ]) *
(2*q*Cos[θ]^2 - q*Sin[θ]^2)) /
(Sqrt[f0]*r0) +
((-1 + f0)*Cos[ϕ]*Sin[θ] *
(q*Cos[θ]^2*Cos[ϕ]^2 -
2*q*Cos[ϕ]^2*Sin[θ]^2 +
q*Sin[ϕ]^2)) / (Sqrt[f0]*r0) +
(2*Sqrt[f0]*Cos[ϕ]*Sin[θ] *
(q*Cos[ϕ]^2 +
q*Cos[θ]^2*Sin[ϕ]^2 -
2*q*Sin[θ]^2*Sin[ϕ]^2)) / r0;
With ParallelTable
(no SharedVariable
). Only the outer loop is parallelized, which means one kernel gets 5 integrals (and more of the longer ones):
(times = Flatten[
ParallelTable[
(Integrate[expr SphericalHarmonicY[l, m, θ, ϕ] Sin[θ],
{θ, 0, π}, {ϕ, 0, 2 π}];
$KernelID) //
AbsoluteTiming,
{l, 0, lmax}, {m, 0, l}],
1]) // AbsoluteTiming
(*
{16.955, {{1.0218, 10}, {1.05096, 9}, {4.39347, 9}, {1.08692,
8}, {4.31533, 8}, {3.46061, 8}, {1.0968, 7}, {4.706, 7}, {3.45951,
7}, {3.59418, 7}, {1.05576, 6}, {4.38848, 6}, {3.4545,
6}, {3.47644, 6}, {3.29451, 6}}}
*)
Merge[Apply[Rule]@*Reverse /@ times, List]
Total@*Flatten /@ %
(* $KernelID -> times of integrals (not all kernels used)
<|10 -> {{1.0218}},
9 -> {{1.05096, 4.39347}},
8 -> {{1.08692, 4.31533, 3.46061}},
7 -> {{1.0968, 4.706, 3.45951, 3.59418}},
6 -> {{1.05576, 4.38848, 3.4545, 3.47644, 3.29451}}|>
<|10 -> 1.0218, 9 -> 5.44443, 8 -> 8.86287, 7 -> 12.8565, 6 -> 15.6697|>
*)
@ShinKim's method with SharedVariable
; the table just returns the time and kernel of each result.
ind = Flatten[Table[{l, m}, {l, 0, lmax}, {m, 0, l}], 1];
result = Table[0, {l, 0, lmax}, {m, 0, l}];
SetSharedVariable[result];
(times = ParallelTable[
({l, m} = ind[[i]];
result[[l + 1, m + 1]] =
Integrate[expr SphericalHarmonicY[l, m, θ, ϕ] Sin[θ],
{θ, 0, π}, {ϕ, 0, 2 π}];
$KernelID) //
AbsoluteTiming,
{i, Length@ind}]) // AbsoluteTiming
(*
{9.12166, {{1.3988, 10}, {0.336149, 10}, {5.10557, 9}, {0.597471,
9}, {5.03329, 8}, {3.36767, 8}, {1.4241, 7}, {4.75394,
7}, {4.78472, 6}, {3.54514, 6}, {1.44249, 5}, {5.10244,
4}, {4.73389, 3}, {4.82558, 2}, {4.79167, 1}}}
*)
Merge[Apply[Rule]@*Reverse /@ times, List]
Total@*Flatten /@ %
(* $KernelID -> times of integrals
<|10 -> {{1.3988, 0.33615}}, 9 -> {{5.10557, 0.597471}},
8 -> {{5.03329, 3.36767}}, 7 -> {{1.4241, 4.75394}},
6 -> {{4.78472, 3.54514}}, 5 -> {{1.44249}},
4 -> {{5.10244}}, 3 -> {{4.73389}},
2 -> {{4.82558}}, 1 -> {{4.79167}}|>
<|10 -> 1.73495, 9 -> 5.70304, 8 -> 8.40097, 7 -> 6.17804, 6 -> 8.32986,
5 -> 1.44249, 4 -> 5.10244, 3 -> 4.73389, 2 -> 4.82558, 1 -> 4.79167|>
*)
Unparallelized:
(times = Flatten[
Table[(
Integrate[(expr SphericalHarmonicY[l, m, θ, ϕ] Sin[θ]),
{θ, 0, π}, {ϕ, 0, 2 π}];
$KernelID) //
AbsoluteTiming,
{l, 0, lmax}, {m, 0, l}],
1]) // AbsoluteTiming
(*
{20.596, {{0.923452, 0}, {0.242653, 0}, {4.09701, 0}, {0.301539,
0}, {0.865541, 0}, {3.20008, 0}, {0.291668, 0}, {1.18621,
0}, {0.579081, 0}, {3.30374, 0}, {0.254854, 0}, {0.894277,
0}, {0.600397, 0}, {0.670284, 0}, {3.18521, 0}}}
*)
Merge[Apply[Rule]@*Reverse /@ times, List]
Total@*Flatten /@ %
(*
<|0 ->
{{0.923452,
0.242653, 4.09701, <-- head of column is longest
0.301539, 0.865541, 3.20008, ↙ ↓
0.291668, 1.18621, 0.579081, 3.30374,
0.254854, 0.894277, 0.600397, 0.670284, 3.18521}}|>
<|0 -> 20.596|>
*)
My guess is that each new value of m
causes something to be cached or code to be initialized. You lose this efficiency in parallelization. For instance, switching the iterators in the first ParallelTable
above to {m, 0, lmax}, {l, m, lmax}
cuts the time in half.
result
is maybe not a good idea... $\endgroup$ParallelTable
instead. $\endgroup$