Skip to main content

performance tuning - Why is `Block` significantly faster than `ReplaceAll` for inputting variable values


Given that the documentation for FindMinimum uses ReplaceAll for its various examples of what to do with the found solution to an optimization problem, it would seem to be the right tool for the job. However, the following MWE highlights the speed problem I ran into using ReplaceAll for such a situation. Is using Block the appropriate tool in this case and why?


vars = Table[f[i], {i, 1, 10000}];
vals = RandomReal[1, Length[vars]];

soln = MapThread[#1 -> #2 &, {vars, vals}];
Total[vars] /. soln // Timing
(*{1.395787, 4980.51}*)

Block[{f},
ReleaseHold[
MapThread[Hold[#1 = #2] &, Transpose[List @@ # & /@ soln]]];
Total[vars]] // Timing
(*{0.033995, 4980.51}*)


A slight variation on the Block approach, which I expect would be equivalent, is also slightly slower (here both have been repeated 1000 times to show the less-significant difference of ~20%). Is there another approach that would achieve the same but faster?


Do[Block[{f}, 
ReleaseHold[
MapThread[Hold[#1 = #2] &, Transpose[List @@ # & /@ soln]]];
Total[vars]], {1000}]; // Timing
(*{26.072036, Null}*)

Do[Block[{f}, (Set @@ # &) /@ (List @@ # & /@ soln);
Total[vars]], {1000}]; // Timing
(*{31.651188, Null}*)


Further testing has found that the problem is exasperated by "bigger" expressions than Total, in particular by the use of ListInterpolation. This appears to be resolved by only Replacing at the right level, which works in this particular example because all the variables have the same levelspec ({-2} or {3}).


vars = Table[f[i], {i, 1, 10000}];
vals = RandomReal[{-1, 1}, Length[vars]];
soln = MapThread[#1 -> #2 &, {vars, vals}];
i = ListInterpolation[RandomReal[1, 40], {-1, 1},
"ExtrapolationHandler" -> {(0 &), "WarningMessage" -> False}];
expr = Plus @@
MapThread[Times, RotateLeft[i@vars, #] & /@ Range[0, 4]];


expr /. Dispatch[soln] // Timing
(*{2.504619, 309.407}*)

Replace[expr, Dispatch[soln], {-2}] // Timing
(*{0.124981, 309.407}*)

Block[{f},
ReleaseHold[
MapThread[Hold[#1 = #2] &, Transpose[List @@ # & /@ soln]]];
expr] // Timing

(*{0.146978, 309.407}*)

In the actually problem I am facing, not all the variables have the same levelspec, so it seems to require the range {-2,-1}, which ends up taking slightly longer than Block.



Answer



Original question


As noted in the comments the use of Dispatch is the easiest way to make this replacement operation much faster. However taking this as an opportunity to explore other optimizations here are some examples for you to consider:


(dsp = Dispatch[soln]) // RepeatedTiming // First


0.0030


Total[vars] /. dsp // RepeatedTiming


{0.0038, 5025.19}

It is faster if we focus the replacement on one level alone:


Replace[Total[vars], dsp, {-2}] // RepeatedTiming



{0.0032, 5025.19}

Interestingly levelspec {1} appears slightly faster than {-2}:


Replace[Total[vars], dsp, {1}] // RepeatedTiming


{0.0030, 5025.19}

Moving the Total outside prevents a wasted summation attempt:


Total @ Replace[vars, dsp, {1}] // RepeatedTiming



{0.0019, 5025.19}

Lookup on an Association is faster however:


asc = Association[soln];

Total @ Lookup[asc, vars] // RepeatedTiming



{0.0017, 5025.19}

Finally, Tr is faster than Total on packed arrays, and packing has low overhead here:


Tr @ Developer`ToPackedArray @ Lookup[asc, vars] // RepeatedTiming


{0.00088, 5025.19}

Updated question


In your update expr is a large expression containing many InterpolatingFunction subexpressions. Each of these is significant:



expr[[1, 1]] // LeafCount    (* 153 *)

Further they each contain multiple packed arrays:


expr[[1, 1, 0]] /. x_List?Developer`PackedArrayQ :> Print[Short@x];


{40}

{4}


{-1.,-0.948718,<<37>>,1.}

{0,1,2,3,4,5,6,7,8,<<23>>,32,33,34,35,36,37,38,39,40}

{0.947691,<<38>>,0.386701}

All of this is wasted space to search for a pattern match and every packed array must be first unpacked incurring additional overhead. Since you only want to replace expressions in the form f[_] and since these all occur in the same place in each subexpression it will be far better to target the replacement accordingly. That is exactly what you did with Replace[expr, Dispatch[soln], {-2}] which is faster than Block in this case. (Indicientally Block avoids this problem because the evaluator will have flagged the InterpolatingFunction expressions as already evaluated and not depending on f and therefore not requiring updating, as part of its optimizations.)


Since the long InterpolatingFunction expressions appear as heads you could also use Heads -> False with almost the same efficiency even with Infinity as the depth:


Replace[expr, Dispatch[soln], {-2}] // Timing
Replace[expr, Dispatch[soln], ∞, Heads -> False] // Timing



{0.121, 319.564}

{0.145, 319.564}

But at least for this example it brings up another question: why aren't you doing the replacement on vars first and then building expr?


With[{ivars = i @ Replace[vars, Dispatch[soln], {1}]},
Times @@ NestList[RotateLeft, ivars, 4] // Total
] // Timing // First



0.0237



By the way if all your replacements are of the form shown there is a simpler way to use Block:


Block[{f},
DownValues[f] = soln;
expr
] // Timing



{0.143, 319.564}

Comments

Popular posts from this blog

plotting - Plot 4D data with color as 4th dimension

I have a list of 4D data (x position, y position, amplitude, wavelength). I want to plot x, y, and amplitude on a 3D plot and have the color of the points correspond to the wavelength. I have seen many examples using functions to define color but my wavelength cannot be expressed by an analytic function. Is there a simple way to do this? Answer Here a another possible way to visualize 4D data: data = Flatten[Table[{x, y, x^2 + y^2, Sin[x - y]}, {x, -Pi, Pi,Pi/10}, {y,-Pi,Pi, Pi/10}], 1]; You can use the function Point along with VertexColors . Now the points are places using the first three elements and the color is determined by the fourth. In this case I used Hue, but you can use whatever you prefer. Graphics3D[ Point[data[[All, 1 ;; 3]], VertexColors -> Hue /@ data[[All, 4]]], Axes -> True, BoxRatios -> {1, 1, 1/GoldenRatio}]

plotting - Filling between two spheres in SphericalPlot3D

Manipulate[ SphericalPlot3D[{1, 2 - n}, {θ, 0, Pi}, {ϕ, 0, 1.5 Pi}, Mesh -> None, PlotPoints -> 15, PlotRange -> {-2.2, 2.2}], {n, 0, 1}] I cant' seem to be able to make a filling between two spheres. I've already tried the obvious Filling -> {1 -> {2}} but Mathematica doesn't seem to like that option. Is there any easy way around this or ... Answer There is no built-in filling in SphericalPlot3D . One option is to use ParametricPlot3D to draw the surfaces between the two shells: Manipulate[ Show[SphericalPlot3D[{1, 2 - n}, {θ, 0, Pi}, {ϕ, 0, 1.5 Pi}, PlotPoints -> 15, PlotRange -> {-2.2, 2.2}], ParametricPlot3D[{ r {Sin[t] Cos[1.5 Pi], Sin[t] Sin[1.5 Pi], Cos[t]}, r {Sin[t] Cos[0 Pi], Sin[t] Sin[0 Pi], Cos[t]}}, {r, 1, 2 - n}, {t, 0, Pi}, PlotStyle -> Yellow, Mesh -> {2, 15}]], {n, 0, 1}]

plotting - Mathematica: 3D plot based on combined 2D graphs

I have several sigmoidal fits to 3 different datasets, with mean fit predictions plus the 95% confidence limits (not symmetrical around the mean) and the actual data. I would now like to show these different 2D plots projected in 3D as in but then using proper perspective. In the link here they give some solutions to combine the plots using isometric perspective, but I would like to use proper 3 point perspective. Any thoughts? Also any way to show the mean points per time point for each series plus or minus the standard error on the mean would be cool too, either using points+vertical bars, or using spheres plus tubes. Below are some test data and the fit function I am using. Note that I am working on a logit(proportion) scale and that the final vertical scale is Log10(percentage). (* some test data *) data = Table[Null, {i, 4}]; data[[1]] = {{1, -5.8}, {2, -5.4}, {3, -0.8}, {4, -0.2}, {5, 4.6}, {1, -6.4}, {2, -5.6}, {3, -0.7}, {4, 0.04}, {5, 1.0}, {1, -6.8}, {2, -4.7}, {3, -1....