Wolfram Language Speed Guide

By Xah Lee. Date: . Last updated: .

Here's a guide to speed up your WolframLang code.

Note: things discussed here are sensible only if your data is huge, such as list with thousands or millions items.

General Principle

most important first.

  1. Do not repeatedly add/delete element to a List. Because WolframLang List is an array in computer science sense. Everytime list length changes, the list is recreated, and that is slow, especially when there are large number of items. Instead, leave the items there as is, extract element in final step. Or, create a large list in the beginning, and change items to some random symbol (e.g x1234) when you don't need them. Or, use SparseArray or Association
  2. Any use of Pattern Matching is an order slower than not using pattern. This means, when you define a function, best to define it using Function when you can.
  3. Using Set and SetDelayed, slows down your entire program. [see Set and SetDelayed] because they are global pattern matching, and they are tried for every evaluation of expressions.
  4. When using Pattern Matching on large expression, try to narrow down parts of expression to match, or a level. e.g. use ReplaceAt at a Position , Replace at a level, instead of ReplaceAll.
  5. Avoid Patterns that match nested structure or repeatition of a complex pattern.
  6. The less number of function calls, the faster. Try to find a WolframLang function that does exactly what you want, instead of constructing multiple function calls.

Table vs Append

Append in a loop is mega slow. The longer the list, the exponentially slower.

nn = 2 * 10^4;

Timing[ xx = Table[ 1, {nn} ]  ]//First
(* 0. *)

Timing[ yy = {}; Do[ AppendTo[ yy, 1 ], {nn} ] ]//First
(* 0.75 *)

xx === yy
(* True *)

Save Repeating Costy Expression to a constant

if you have a complex expression repeated, use a temp constant for the expression to compute it just once.

(* list of random vector, each is 10^7 dimension *)
dd = Table[RandomReal[{-100, 100}], {10}, {10^7}];

(* suppose this is slow function *)
xCostly = ((# . #) &);

(* compute just once, save to a constant *)
f1 = With[{x = xCostly[#] }, If[ x < 0.001 , #, #/x] ] &;

(* repeated computation *)
f2 = If[ (xCostly[#]) < 0.001 , #, #/(xCostly[#])] &;

Timing[rr1 = f1 /@ dd]//First
(* 1.32 *)

Timing[rr2 = f2 /@ dd]//First
(* 2.21 *)

rr1 == rr2

Pure Function vs Pattern Matching Function

Using pattern matching such as f[x_] := x is about 20 times slower than using f = Function[#].

f1[x_] := x;
f2 = (#)&;

dd = Table[ RandomInteger[ 100 ], {10^7}];

x1 = Timing[ f1 /@ dd ];
x2 = Timing[ f2 /@ dd ];

First@x1
(* 2.75 *)

First@x2
(* 0.09375 *)

First@x1 / First@x2
(* f1 is 20 to 30 times slower *)

Transpose is constant time and instantaneous

Transpose is constant time and instantaneous, regardless of matrix size. It is a great trick to use it for lots situations.

(* Transpose is instantaneous and constant time.
regardless of matrix size
 *)

xx = Table[ {x, y}, {x, 3}, {y, 3} ];

yy = Table[ {x, y}, {x, 10^3}, {y, 10^3} ];

Timing[ Transpose[ xx ] ]//First
(* 0. *)

Timing[ Transpose[ yy ] ]//First
(* 0. *)

Select, Cases, Exit When Found

When using Select, and if you just want first found, give an argument that specifies the max count of item to return. It basically exit the search when the item is found. Or use SelectFirst

xx = Table[ 0 , {10^7} ];
xx[[9]] = 1;

Timing[ Select[ xx, OddQ ] ]
(* {1.703125, {1}} *)

(* exit when found first *)
Timing[ Select[ xx, OddQ, 1 ] ]
(* {0.078125, {1}} *)

When using Cases, and if you just want first found, give an argument that specifies the max count of item to return. Or use FirstCase

xx = Table[ RandomInteger[ 100 ] , {10^7} ];

rr1 = Timing[ Cases[ xx, 2 ] ];

(* exit immediately when found *)
rr2 = Timing[ Cases[ xx, 2, {1}, 1 ] ];

rr1[[1]]
(* 0.34 *)

rr2[[1]]
(* 0.14 *)

(* rr1 is twice as slow *)

Select vs Cases

Cases is slightly slower when using the same function test.

xx = Table[ RandomInteger[ {0,1} ] , {10^6} ];

Timing[ Select[ xx, OddQ ] ]//First
(* 0.18 *)

Timing[ Cases[ xx, _?OddQ ] ]//First
(* 0.25 *)

(* Cases is slightly slower *)

Pattern matching, _ vs _Integer

the addition of checking head _Integer does not seems to effect speed.

xx = Table[ RandomInteger[ {0,100} ], {10^7} ];

rr1 = Timing[ Cases[ xx, _ ] ];

rr2 = Timing[ Cases[ xx, _Integer ] ];

First@rr1
(* 0.0625 *)

First@rr2
(* 0.078125 *)

First@rr1 / First@rr2
(* 0.8 *)
(* rr1 is slightly faster *)

WolframLang Table vs Array

Table may be faster than using Array

n=100;

xx1= Timing@ Table[Cube[{x,y,z},1], {x,-n,n}, {y,-n,n}, {z,-n,n}];
First[ xx1 ]
(* 2.5 *)

xx2= Timing@ Array[Cube[{#1,#2,#3},1]&, {2 n+1,2 n+1,2 n+1}, {-n,-n,-n}];
First[ xx2 ]
(* 3.0 *)

xx1[[2]] == xx2[[2]]
(* True *)

Apply Plus vs Total

using code construct such as (Plus@@#)& is a bit slower than using builtin function such as Total[#] &.

f1 = (Plus@@#)&;
f2 = Total[#] &;

xVecLength = 3;
xCount = 10^6;
dd = Table[ RandomReal[], {xCount}, {xVecLength}];

rr = (( (Timing[# /@ dd]) &) /@ {f1,f2});

rr[[1,1]] / rr[[2,1]]
(* f1 takes 1.2 times longer *)

Equal[ rr[[1,2]], rr[[2,2]]  ]

Total vs Norm

same speed.

f1 = #/Norm[#]^2 &;
f2 = #/Total[#^2] &;

xVecLength = 3;
xCount = 10^6;
dd = Table[ RandomReal[], {xCount}, {xVecLength}];

rr = (( (Timing[# /@ dd]) &) /@ {f1,f2});

rr[[1,1]]
(* 0.34375 *)

rr[[2,1]]
(* 0.3125 *)

rr[[1,1]] / rr[[2,1]]
(* 1.05 *)
(* f1 f2 are same speed *)

Equal[rr[[1,2]] , rr[[2,2]]]

Total[v^2] vs v.v

v.v is faster than Total[v^2] , not perceivable. At least if the vector components are real numbers.

dd = Table[RandomReal[{-10, 10}], {10^6}, {3}];

f1 = Total[#^2] &;
f2 = # . # &;

xx1 = Timing[ f1 /@ dd ];
xx2 = Timing[ f2 /@ dd ];

First@xx1
(* 0.12 *)

First@xx2
(* 0.1 *)

(* same speed *)

xx1[[2]] == xx2[[2]]

when the vector length is millions, # . # & is 10 times faster than Total[#^2] &

dd = Table[RandomReal[{-10, 10}], {10^8}];

f1 = Total[#^2] &;
f2 = # . # &;

xx1 = Timing[ f1 @ dd ];
xx2 = Timing[ f2 @ dd ];

First@xx1
(* 0.15 *)

First@xx2
(* 0.015 *)

xx1[[2]] == xx2[[2]]

Function arg sequence ##

Using ## is slower than using explicit #1,#2,#3

n=100;

xx1 = Timing@ Array[ff[{##}, 1]&, {n, n, n }, {-n,-n,-n}];
First@xx1
(* 0.45 *)

xx2 = Timing@ Array[ff[{#1, #2, #3}, 1]&, {n, n, n }, {-n,-n,-n}];
First@xx2
(* 0.35 *)

xx1[[2]] == xx2[[2]]