# Unexpected optimization with With

I do not understand why the function With[{y = #}, {y, y, y}] & maps faster than the function {#, #, #} &. The difference is appreciable in my system.

Module[{p = RandomInteger[1000, 100], n = 1000000,
f = Function[Null, {#, #, #}],
g = Function[Null, {#, #, #}, HoldAll]}, {
Timing[Do[With[{y = #}, {y, y, y}] & /@ p, {n}]],
Timing[Do[{#, #, #} & /@ p, {n}]],
Timing[Do[f /@ p, {n}]],
Timing[Do[g /@ p, {n}]]}]

(*{{9.031250, Null}, {11.078125, Null}, {11.125000, Null}, {11.203125, Null}}*)


I thought that the time saving came from With having attribute HoldAll but the timing of the function g above is similar to the other slower functions.

Is there any rule of thumbs to use With to speed up the code?

The question has been partially answered by Alexey's comment below. Nevertheless, would the following rule be valid (?): Compiled numerical functions that use the input in several places benefit from With.

• On my laptop I get {15.0229, 16.8793, 17.0353, 17.1757} as timings. The difference is small but really surprising. Probably it is somehow related to auto-compilation. – Alexey Popkov Aug 9 '16 at 6:53
• @AlexeyPopkov: you were right on target! Changing p = RandomInteger[1000, 100] to p = RandomInteger[1000, 50] results in a drastic increase of the timings {78.6, 27.6, 27.7, 36.5}. The function with With is now slower as expected. – Hector Aug 9 '16 at 7:16

Following Alexey's supposition we can indeed find a difference in the compilation of these functions. I shall compare only two forms as the other cases appear similar.

Needs["CompiledFunctionTools"]

f1 = With[{y = #}, {y, y, y}] &;
f2 = {#, #, #} &;

{cf1, cf2} = Compile[{{x, _Integer, 1}}, # /@ x] & /@ {f1, f2};

CompilePrint /@ {cf1, cf2}


Abridged output:

1   I2 = Length[ T(I1)0]
2   I5 = I6
3   T(I2)2 = Table[ I2, I5]
4   I4 = I7
5   goto 9
6   I8 = GetElement[ T(I1)0, I4]
7   T(I1)3 = {I8, I8, I8}
8   Element[ T(I2)2, I5] = T(I1)3
9   if[ ++ I4 <= I2] goto 6
10  Return

1   I2 = Length[ T(I1)0]
2   I6 = I8
3   T(I2)2 = Table[ I2, I6]
4   I4 = I9
5   goto 11
6   I5 = GetElement[ T(I1)0, I4]
7   I7 = GetElement[ T(I1)0, I4]
8   I10 = GetElement[ T(I1)0, I4]
9   T(I1)3 = {I5, I7, I10}
10  Element[ T(I2)2, I6] = T(I1)3
11  if[ ++ I4 <= I2] goto 6
12  Return


So it seems that With does make it down into the compiled function in the form of a single GetElement operation rather than three. What surprises me is that this is not already the way that a Function compiles.

• Would you care to speculate on the reason for the lack of optimization? Is there a way for a parameter to change its value each time it is evaluated in a compiled function? I can think of an example using a function with Hold attributes, but I fail to devise an example with a compiled function (which implies numerical parameters AFAIK). – Hector Aug 9 '16 at 23:30
• @Hector as I said it surprises me. I cannot off-hand think of a reason for {#, #, #} & not to compile the same as With[{y = #}, {y, y, y}] & since as I believe you correctly note the argument must be numeric. Without your question (thanks) I would have assumed that any Function compilation would start by defining variables e.g. I8 = GetElement[ T(I1)0, I4] and then referencing them, e.g. {I8, I8, I8}, rather than repeated GetElement operations. – Mr.Wizard Aug 10 '16 at 2:58
• f3 = {##, ##, ##} &; does compile the expected way (to the same CompiledFunction as f1). – Karsten 7. Aug 10 '16 at 17:51
• @Karsten Very interesting. I would not have anticipated that. Does this make sense to you? – Mr.Wizard Aug 10 '16 at 18:06
• The reason I tried that f3 was, that # is Slot[1], which is kind of like [[1]] but for a Sequence instead of a List. Nevertheless such a behavior is neither optimal nor expected, especially when you write f2 as f2 = Function[x, {x, x, x}];`. – Karsten 7. Aug 10 '16 at 18:33