1
$\begingroup$

While I have seen a lot of documentation regarding the inefficiencies of loops in Mathematica, I am still curious as to why-- while I know it is intentionally developed this way to allow for improved functionality elsewhere, what in the barebones of the language actually causes the loop inefficiencies?

$\endgroup$
  • 6
    $\begingroup$ I think it's the same in any scripting language: repeatedly calling functions (the inside of the loop) is much less efficient than calling one function that does all the work at low level. Every switch between the script level (the loop infrastructure) and the execution level (the loop body) has an overhead. Further, list processing functions can use hardware acceleration tricks like vectorization that script-level loops cannot access. This topic has been discussed at length in this and related fora. $\endgroup$ – Roman Apr 8 '19 at 16:24
  • 6
    $\begingroup$ You might want to look at mathematica.stackexchange.com/questions/2158/… for For loops specifically. Most of the other explicit loop constructs (While and Do) would be most effective in such situations as well. However, because of the guarantees made by the stricter iteration constructs (e.g. Map), much more can be automatically performed to make them faster overall. For loops, on the other hand, can't be guaranteed to terminate at all for all interior expressions, much less parallelize. $\endgroup$ – eyorble Apr 8 '19 at 17:07
  • 2
    $\begingroup$ @Roman I think it would be more useful to post this as an answer than to close the question. I fully agree with what you said. It's not that loops are slow, it's that executing Mathematica code is slow. Using a single function that does everything is potentially faster as that function can be implemented in a lower-level language and optimized. Using a loop to implement the same yourself in Mathematica is going to be slower. $\endgroup$ – Szabolcs Apr 9 '19 at 12:29
  • $\begingroup$ @Szabolcs what I wrote is neither an answer nor a solution, it's merely a comment on an intuition from having written a Forth interpreter in the 1990s. Only somebody with deep knowledge of the Mathematica internals can answer this question. There are lots of subtleties, like just-in-time compilation of loop bodies, pattern-matching entire loops to a database of common loop functionalities (like gcc does to some extent), etc. that I would have no way of addressing here. $\endgroup$ – Roman Apr 9 '19 at 15:07
5
$\begingroup$

I'll try to list some reasons, though the following will not be exhaustive.


Mathematica being a high-level interpreted language, Mathematica code is generally slower than what is possible e.g. in C.

Writing something as a loop means that the loop body must be repeatedly executed by the Mathematica interpreter. If the same operation can be expressed as a single function call, there is a possibility to implement in (internally) in a lower-level language and allow it to run much faster.

Example:

Table[x^2, {x, 1, 10000000}]; // RepeatedTiming
(* {0.168, Null} *)

Range[1, 10000000]^2; // RepeatedTiming
(* {0.029, Null} *)

Doing arithmetic on an entire array, as above, is called vectorization. The internal implementation of such operations will often make use of multiple CPU cores as well as SIMD instructions, both of which would be basically impossible when the loop body can be arbitrary Mathematica code.


Along the same line of thought, using a construct that involves more pieces of Mathematica code being evaluated will be slower.

Example:

AbsoluteTiming[
 arr = ConstantArray[0, 100000];
 For[i = 1, i <= Length[arr], ++i,
  arr[[i]] = i^2
  ];
 ]
(* {0.136787, Null} *)

AbsoluteTiming[
 arr = ConstantArray[0, 100000];
 Do[
  arr[[i]] = i^2,
  {i, Length[arr]}
  ];
 ]
(* {0.086007, Null} *)

Observe that the For needs to evaluate more pieces of Mathematica code. Setting i=1 is explicit, incrementing it is explicit, comparing it to Length[arr] is also explicitly. Some of these are done internally in Do so they can be faster.


Arguably Table is also a looping construct, but it is of a different sort. The body of the Table does not need to have side effects. Setting a value, as in arr[[i]] = ..., is a side effect of evaluating =. Thus an operation like the above requires side effects with Do and For, but not with Table.

Functional construct with no side effects are much more amenable to automatic optimization. Table in particular can automatically Compile its body to allow it to run faster. This is why we get so much better performance with the following:

Table[i^2, {i, 100000}]; // RepeatedTiming
(* {0.0015, Null} *)

Such constructs are also very straightforward to parallelize. Just switch the Table out for a ParallelTable.


The general idea is that if you use programming constructs that express what you are doing in a more specific way, the system has more freedom to choose the fastest way to run it.

Writing code in terms of loops basically means spelling out each small step of the algorithm. It does not convey your high-level goal in a manner that a computer can understand—instead it focuses on the details. Compare "take the square of each element of this array" to the same written as a For loop. The For-based version will necessarily specify details such as initialize an array, initialize an iterator, increment an iterator, compare the iterator to a value and decide what to do based on the result, etc. The very specific algorithm that you express with a typical For loop is not how the faster alternatives (such as Table or vectorized Power) work internally. By specifying each step, you forbid the system for doing anything else.

The takeaway is that when programming in high-level languages, it is better to express our algorithms with higher-level constructs. These higher level constructs can have very efficient internal implementations.

$\endgroup$

Not the answer you're looking for? Browse other questions tagged or ask your own question.