12
$\begingroup$

Let's say I write

y = x^2; x = 10; z = y + y^2 + y^3 

Does Mathematica internally evaluate y before or after substituting it into z? I.e., does Mathematica internally evaluate this as

y = x^2 = 100 z = 100 + 100^2 + 100^3 = 1010100 

or as

z = (10^2)^2 + (10^2)^3 + (10^2)^4 = 1010100 

? Obviously in this simple case it doesn't really matter, but if y is an expensive function to calculate, then the former order of evaluation could be much faster than the latter. In general, if I have intermediate variables that are expensive to calculate, will I get a speedup if I exchange the order of the first two lines of code?

$\endgroup$
4
  • 1
    $\begingroup$ Each symbol can have a value associated with it or not. When you write y=x^2, then later on, write x=10, then y now automatically becomes 100 since it was pointing to x before, but now x has a value which is 10. There is no explicit evaluation of y. It is done automatically. It is all linked lists. If a symbol x has value other than itself, then its value is used automatically when looking up y. $\endgroup$ Commented Jan 11, 2016 at 8:04
  • $\begingroup$ @Nasser this is exactly what OP is asking about. Are dependencies between rules automatically eliminated, or not? It seems that the answer is not, in most cases anyway. This reminds me of another question, but I can't put my finger on it at the moment. $\endgroup$ Commented Jan 11, 2016 at 8:42
  • 1
    $\begingroup$ Aside: Mathematica is holding onto the symbolic definition of y = x^2. If you clear x the value of y will be x^2 not 100. If you had defined x=10 first then the value of y would be 100 before and after x is cleared. $\endgroup$ Commented Jan 11, 2016 at 9:03
  • $\begingroup$ reference.wolfram.com/language/tutorial/Evaluation.html $\endgroup$ Commented Jan 11, 2016 at 9:33

4 Answers 4

14
$\begingroup$

Try Trace:

 Trace[y = x^2; x = 10; z = y + y^2 + y^3] 

The result is enter image description here

From this result it looks like the second variant takes place.

Have fun!

$\endgroup$
7
$\begingroup$

Yes, x^2 is computed three times. One way to see it is this:

In[1]:= On[] On::trace: On[] --> Null. In[2]:= y = x^2 2 2 Set::trace: y = x --> x . 2 Out[2]= x In[3]:= x = 10 Set::trace: x = 10 --> 10. Out[3]= 10 In[4]:= z = y + y^2 + y^3 2 y::trace: y --> x . x::trace: x --> 10. 2 2 Power::trace: x --> 10 . 2 Power::trace: 10 --> 100. 2 y::trace: y --> x . x::trace: x --> 10. 2 2 Power::trace: x --> 10 . 2 Power::trace: 10 --> 100. 2 2 Power::trace: y --> 100 . 2 Power::trace: 100 --> 10000. 2 y::trace: y --> x . x::trace: x --> 10. 2 2 Power::trace: x --> 10 . 2 Power::trace: 10 --> 100. 3 3 Power::trace: y --> 100 . 3 Power::trace: 100 --> 1000000. 2 3 Plus::trace: y + y + y --> 100 + 10000 + 1000000. Plus::trace: 100 + 10000 + 1000000 --> 1010100. 2 3 Set::trace: z = y + y + y --> z = 1010100. Set::trace: z = 1010100 --> 1010100. Out[4]= 1010100 In[5]:= Off[] 

I find this easier to follow than Trace but it produces too much junk in recent versions when run in the front end, so I had to run it in a terminal.

This observation does not imply that the result of y -> x^2 -> 10^2 -> 100 is not cached! (I don't know if it is.)

You can re-assign y form x^2 to simply100as simply asy = y` in this case. Better, you could make it a function and use memoization to cache the result:

Clear[y] y[x_] := y[x] = x^2 
$\endgroup$
5
$\begingroup$

Yes, it seems the order can matter, at least when using SetDelayed (:=), which is common for functions. In that case, if you swapped the first two lines, it would indeed go faster.

One way to see this is with a very inefficient function, which calculates Fibonacci numbers the long way:

slowFib[n_] := If[n <= 2, 1, slowFib[n - 1] + slowFib[n - 2]]; 

On my machine, it takes about 3.2 seconds to calculate the 29th Fibonacci number this way:

slowFib[29] // AbsoluteTiming (* {3.23395, 514229} *) 

Here is a slow calculation, with the same ordering as your example code. It takes 9.7 seconds to run the last line, because it is running slowFib 3 times to calculate y1:

y1 = slowFib1[29]; slowFib1 = slowFib; z1 = y1 + y1 + y1 // AbsoluteTiming (* {2.*10^-6, slowFib1[29]} {1.*10^-6, slowFib} {9.65774, 1542687} *) 

Compare this to a "fast" version with the first 2 lines swapped. It calculates the value of slowFib[29] just once, when it assigns the value to y2, so it only does so once, and takes 3.3 seconds total:

slowFib2 = slowFib ) // AbsoluteTiming (y2 = slowFib2[29]) // AbsoluteTiming (z2 = y2 + y2 + y2) // AbsoluteTiming (* {1.*10^-6, slowFib} {3.28709, 514229} {4.*10^-6, 1542687} *) 

What's going on here is that Mathematica only evaluates slowFib[29] when it is called. In the fast code (vesion 2), it gets called once when defining y2. In the slow code (version 1), it gets called thrice, when defining z1.

$\endgroup$
2
  • $\begingroup$ Indeed, I just tried swapping the order of the definitions in my actual application (much more complicated than the example in the OP) and got a significant speedup. $\endgroup$ Commented Jan 11, 2016 at 8:58
  • 1
    $\begingroup$ If you are only evaluating your functions at a few values, take a look at tip #5 on this list: write your expensive functions as f[x_]:=f[x]=…, so that values get saved as you go. $\endgroup$ Commented Jan 11, 2016 at 9:03
1
$\begingroup$

I'm not sure that memoization applies to OPs question. In a direct test, the order doesn't seem to make much of a difference in execution time.

enter image description here

$\endgroup$

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.