The issue outlined here seems to be no longer present in version 10, if you use a Dispatch
table! This because a Dispatch
table is now an atom.
My question arose when I tried to answer this question by Mr.Wizard
Consider these definitions
Clear @@ Names["x" ~~ DigitCharacter ..]
rules2 = Array[Symbol["x" <> ToString[#]] -> # &, 100000 ];
dRules2 = Dispatch[rules2];
We have
AbsoluteTiming[Symbol["x" <> "100000"] /. dRules2]
{0.000956,100000} (*version 9*) {0.000022,100000} (*version 10*)
AbsoluteTiming[Symbol["x" <> "100000"] /. rules2]
{0.005132,100000} (*version 9*) {0.100221,100000} (*first time version 10*) {0.014408,100000} (*after first time version 10*)
It was to be expected that the dispatch tables are faster. It seems Dispatch
tables became faster in V10, but regular replacement by a long list of rules become considerably slower. That is not the point here however, point is that the following breaks the dispatch table in version 9.
Update[x10]
AbsoluteTiming[Symbol["x" <> "100000"] /. dRules2]
{0.076655,100000} (*version 9*) {0.000026,100000} (*version 10*)
Update[x10]
AbsoluteTiming[Symbol["x" <> "100000"] /. rules2]
{0.017523,100000} (*version 9*) {0.063556,100000} (*version 10*)
Where all the evaluations have slowed down substantially (except there is no "first time delay" in version 10). The version 9 dispatch table is slower than the regular list of rules. I do not expect many people to use Update
, but this should still be relevant to many people, as I believe an assignment causes an update. See an example further below.
I found out that updating a variable present in the list of rule causes the expression (list/dispatch table) containing the rules to be rebuilt. This takes a long time.
We can see that the rules get rebuilt from the following examples. Let
rules = {y1 -> 1, y2 -> 2, y3 -> 3};
Then
Trace[y1 /. rules, TraceOriginal -> True]
{y1/. rules,{ReplaceAll},{y1},{rules,{y1->1,y2->2,y3->3}},y1/. {y1->1,y2->2,y3->3},1}
and
Update[y3]
Trace[y1 /. rules, TraceOriginal -> True]
{y1/. rules,{ReplaceAll},{y1},{rules,{y1->1,y2->2,y3->3},{List},{y1->1},{y2->2}, {y3->3,{Rule},{y3},{3},y3->3},{y1->1,y2->2,y3->3}},y1/. {y1->1,y2->2,y3->3},1}
In the second case we see the Trace is longer. We basically see that the expression rules
gets rebuilt. The rule containing y3
gets a little more attention.
An example where this does not happen is the following. Let
blockRules = {HoldComplete[x, 1], HoldComplete[y, 2],
HoldComplete[z, 3], HoldComplete[x, 4]};
Then we have
Equal @@ {Trace[Cases[blockRules, _[x, y_] -> y, 1, 1],
TraceOriginal -> True]
Update[z];
Trace[Cases[blockRules, _[x, y_] -> y, 1, 1],
TraceOriginal -> True]}
True
despite the fact that we updated. HoldComplete prevents the tracking of symbols and therefore the expression does not get rebuilt.
The point is that I find this behaviuor of Dispatch
(and also the lists of rules) very troubling. Note that HoldPattern will not save you, it does not have attribute HoldAllComplete, so even lists of rules with this wrapper will get rebuilt. Lists of rules will always contain many symbols, so the risk of updating one is very high I'd say.
To be complete, here is an example using HoldPattern. We have
hPRules = {HoldPattern[y1] -> 1, HoldPattern[y2] -> 2,
HoldPattern[y3] -> 3};
Unequal @@ {Trace[y1 /. hPRules, TraceOriginal -> True], Update[y3];
Trace[y1 /. hPRules, TraceOriginal -> True]}
True
The same happens for assignment. To be really complete, here is another example
Composition[Length,
Flatten] /@ {Trace[y1 /. hPRules, TraceOriginal -> True], y3 = 2;
Trace[y1 /. hPRules, TraceOriginal -> True]}
{7, 18}
Where again our list of rules got rebuilt.
Well I guess the questions are a bit contrived. To share my worries was the main point. But here it is I guess:
In version 9, should we be very careful with Dispatch
and lists of Rule
s? Does anybody know a good way around this?
Update
, butUpdate
is a rather special command. In my experience, I had at most a dozen cases where I really needed to useUpdate
(i.e. it was crucial). I would think that the general message we should take home from your observation is that it is best to not use l-values (symbols or expressions which can be assigned values) as l.h.s. of rules (also inDispatch
), but I more or less follow this practice in any case. $\endgroup$Update
and before the substitution the substitution is not slowed down. So perhaps you need to "overload"Update
in a such fashion that will also update the rules if a symbol contained in one is updated. $\endgroup$HoldPattern
is used is to prevent some patterns from evaluation. When wrapped around symbols, it is occasionally ok, but this is arguably not its main use. But if I understand correctly, your observation only affects the speed of rule application, not the result. When I have something likeHoldPattern[sym]
, I usually don't care about the speed so much. So, while I agree that you made an interesting observation, I fail to see how this would affect e.g. code I personally write in the 99.9 percent of cases. So, for me, it is just a good warning. Of course, YMMV. $\endgroup$DownValues
). You are right in that we may want to understand this issue a bit better. Good night! $\endgroup$