Compiling Wolfram Language Expressions

If you make a definition like f[x_]:=x (RefLink[Sin, paclet:ref/Sin])[x], the Wolfram Language will store the expression x (RefLink[Sin, paclet:ref/Sin])[x] in a form that can be evaluated for any x. Then when you give a particular value for x, the Wolfram Language substitutes this value into x (RefLink[Sin, paclet:ref/Sin])[x], and evaluates the result. The internal code that the Wolfram Language uses to perform this evaluation is set up to work equally well whether the value you give for x is a number, a list, an algebraic object, or any other kind of expression.

Having to take account of all these possibilities inevitably makes the evaluation process slower. However, if the Wolfram Language could assume that x will be a machine number, then it could avoid many steps, and potentially evaluate an expression like x (RefLink[Sin, paclet:ref/Sin])[x] much more quickly.

Using (RefLink[Compile, paclet:ref/Compile]), you can construct compiled functions in the Wolfram Language, which evaluate Wolfram Language expressions assuming that all the parameters which appear are numbers (or logical variables). (RefLink[Compile, paclet:ref/Compile])[{(Subscript[x, 1]),(Subscript[x, 2]),…},expr] takes an expression expr and returns a "compiled function" which evaluates this expression when given arguments (Subscript[x, 1]), (Subscript[x, 2]),….

In general, (RefLink[Compile, paclet:ref/Compile]) creates a (RefLink[CompiledFunction, paclet:ref/CompiledFunction]) object which contains a sequence of simple instructions for evaluating the compiled function. The instructions are chosen to be close to those found in the machine code of a typical computer, and can thus be executed quickly.

Creating compiled functions.

This defines f to be a pure function which evaluates x (RefLink[Sin, paclet:ref/Sin])[x] for any x:

f=Function[{x},x Sin[x]]

This creates a compiled function for evaluating x (RefLink[Sin, paclet:ref/Sin])[x]:

fc=Compile[{x},x Sin[x]]

f and fc yield the same results, but fc runs faster when the argument you give is a number:

{f[2.5],fc[2.5]}

(RefLink[Compile, paclet:ref/Compile]) is useful in situations where you have to evaluate a particular numerical or logical expression many times. By taking the time to call (RefLink[Compile, paclet:ref/Compile]), you can get a compiled function which can be executed more quickly than an ordinary Wolfram Language function.

For simple expressions such as x (RefLink[Sin, paclet:ref/Sin])[x], there is usually little difference between the execution speed for ordinary and compiled functions. However, as the size of the expressions involved increases, the advantage of compilation also increases. For large expressions, compilation can speed up execution by a factor as large as 20.

Compilation makes the biggest difference for expressions containing a large number of simple, say arithmetic, functions. For more complicated functions, such as (RefLink[BesselK, paclet:ref/BesselK]) or (RefLink[Eigenvalues, paclet:ref/Eigenvalues]), most of the computation time is spent executing internal Wolfram Language algorithms, on which compilation has no effect.

This creates a compiled function for finding values of the tenth Legendre polynomial. (RefLink[Evaluate, paclet:ref/Evaluate]) tells the Wolfram Language to construct the polynomial explicitly before doing compilation:

pc=Compile[{x},Evaluate[LegendreP[10,x]]]

This finds the value of the tenth Legendre polynomial with argument 0.4:

pc[0.4]

This uses built-in numerical code:

LegendreP[10,0.4]

Even though you can use compilation to speed up numerical functions that you write, you should still try to use built-in Wolfram Language functions whenever possible. Built-in functions will usually run faster than any compiled Wolfram Language programs you can create. In addition, they typically use more extensive algorithms, with more complete control over numerical precision and so on.

You should realize that built-in Wolfram Language functions quite often themselves use (RefLink[Compile, paclet:ref/Compile]). Thus, for example, (RefLink[NIntegrate, paclet:ref/NIntegrate]) by default automatically uses (RefLink[Compile, paclet:ref/Compile]) on the expression you tell it to integrate. Similarly, functions like (RefLink[Plot, paclet:ref/Plot]) and (RefLink[Plot3D, paclet:ref/Plot3D]) use (RefLink[Compile, paclet:ref/Compile]) on the expressions you ask them to plot. Built-in functions that use (RefLink[Compile, paclet:ref/Compile]) typically have the option (RefLink[Compiled, paclet:ref/Compiled]). Setting (RefLink[Compiled, paclet:ref/Compiled])->(RefLink[False, paclet:ref/False]) tells the functions not to use (RefLink[Compile, paclet:ref/Compile]).

Specifying types for compilation.

(RefLink[Compile, paclet:ref/Compile]) works by making assumptions about the types of objects that occur in evaluating the expression you give. The default assumption is that all variables in the expression are approximate real numbers.

(RefLink[Compile, paclet:ref/Compile]) nevertheless also allows integers, complex numbers and logical variables ((RefLink[True, paclet:ref/True]) or (RefLink[False, paclet:ref/False])), as well as arrays of numbers. You can specify the type of a particular variable by giving a pattern which matches only values that have that type. Thus, for example, you can use the pattern _Integer to specify the integer type. Similarly, you can use (RefLink[True, paclet:ref/True])|(RefLink[False, paclet:ref/False]) to specify a logical variable that must be either (RefLink[True, paclet:ref/True]) or (RefLink[False, paclet:ref/False]).

This compiles the expression 5i+j with the assumption that i and j are integers:

Compile[{{i,_Integer},{j,_Integer}},5 i+j]

This yields an integer result:

%[8,7]

This compiles an expression that performs an operation on a matrix of integers:

Compile[{{m,_Integer,2}},Apply[Plus,Flatten[m]]]

The list operations are now carried out in a compiled way, and the result is an integer:

%[{{1,2,3},{7,8,9}}]

The types that (RefLink[Compile, paclet:ref/Compile]) handles correspond essentially to the types that computers typically handle at a machine-code level. Thus, for example, (RefLink[Compile, paclet:ref/Compile]) can handle approximate real numbers that have machine precision, but it cannot handle arbitrary-precision numbers. In addition, if you specify that a particular variable is an integer, (RefLink[Compile, paclet:ref/Compile]) generates code only for the case when the integer is of "machine size", typically between ±(2^31).

When the expression you ask to compile involves only standard arithmetic and logical operations, (RefLink[Compile, paclet:ref/Compile]) can deduce the types of objects generated at every step simply from the types of the input variables. However, if you call other functions, (RefLink[Compile, paclet:ref/Compile]) will typically not know what type of value they return. If you do not specify otherwise, (RefLink[Compile, paclet:ref/Compile]) assumes that any other function yields an approximate real number value. You can, however, also give an explicit list of patterns, specifying what type to assume for an expression that matches a particular pattern.

This defines a function which yields an integer result when given an integer argument:

com[i_]:=Binomial[2i,i]

This compiles x^com[i] using the assumption that com[_] is always an integer:

Compile[{x,{i,_Integer}},x^com[i],{{com[_],_Integer}}]

This evaluates the compiled function:

%[5.6,1]

The idea of (RefLink[Compile, paclet:ref/Compile]) is to create a function which is optimized for certain types of arguments. (RefLink[Compile, paclet:ref/Compile]) is nevertheless set up so that the functions it creates work with whatever types of arguments they are given. When the optimization cannot be used, a standard Wolfram Language expression is evaluated to find the value of the function.

Here is a compiled function for taking the square root of a variable:

sq=Compile[{x},Sqrt[x]]

If you give a real number argument, optimized code is used:

sq[4.5]

The compiled code cannot be used, so the Wolfram Language prints a warning, then just evaluates the original symbolic expression:

sq[1+u]

The compiled code generated by (RefLink[Compile, paclet:ref/Compile]) must make assumptions not only about the types of arguments you will supply, but also about the types of all objects that arise during the execution of the code. Sometimes these types depend on the actual values of the arguments you specify. Thus, for example, (RefLink[Sqrt, paclet:ref/Sqrt])[x] yields a real number result for real x if x is not negative, but yields a complex number if x is negative.

(RefLink[Compile, paclet:ref/Compile]) always makes a definite assumption about the type returned by a particular function. If this assumption turns out to be invalid in a particular case when the code generated by (RefLink[Compile, paclet:ref/Compile]) is executed, then the Wolfram Language simply abandons the compiled code in this case, and evaluates an ordinary Wolfram Language expression to get the result.

The compiled code does not expect a complex number, so the Wolfram Language has to revert to explicitly evaluating the original symbolic expression:

sq[-4.5]

An important feature of (RefLink[Compile, paclet:ref/Compile]) is that it can handle not only mathematical expressions, but also various simple Wolfram Language programs. Thus, for example, (RefLink[Compile, paclet:ref/Compile]) can handle conditionals and control flow structures.

In all cases, (RefLink[Compile, paclet:ref/Compile])[vars,expr] holds its arguments unevaluated. This means that you can explicitly give a "program" as the expression to compile.

This creates a compiled version of a Wolfram Language program which implements Newton's approximation to square roots:

newt=Compile[{x,{n,_Integer}},Module[{t},t=x;Do[t=(t+x/t)/2,{n}];t]]

This executes the compiled code:

newt[2.4,6]