Skip to content

Compilation

WhiteBlackGoose edited this page Feb 15, 2023 · 1 revision

We will consider ways to improve the performance of computations via compiling functions.

Compilation into a native delegate

Here we will consider a bunch of methods, which all are overloads of one: Compile. This method compiles a given symbolic expression into a native delegate, which then can be executed with a very high speed. This time, we first consider the way you highly likely need it to be compiled. Let us start with a simple example:

Entity expr = "sin(x)";
Func<float, float> mySin = expr.Compile<float, float>("x");
Console.WriteLine(mySin(4.2f));

Output:

-0.87157565

The variable mySin has type Func<float, float>. Its type parameters accurately correspond to the type parameters of method Compile. Let us consider a more complicated example:

Entity expr = "sqrt(sin(x) + 3)";
var f = expr.Compile<float, float>("x");
Console.WriteLine(f(4.2f));

Output:

1.458912

The number 3, although integer, is upcasted to float automatically. But if only you passed an integer as type params, sin will return an int as well. Example:

Entity expr = "sqrt(sin(x) + 3)";
var f = expr.Compile<int, int>("x");
Console.WriteLine(f(4));

Output:

1

You can also pass multiple parameters, as well as multiple parameters of different types. Example:

Entity expr = "sin(x) + y";
var f = expr.Compile<float, double, float>("x", "y");
Console.WriteLine(f(4.3f, 7.8d));

Output:

6.883834

The supported types are: int, long, float, double, Complex, BigInteger. Let us consider an example, where int is upcasted to a Complex:

Entity expr = "x + y";
var f = expr.Compile<Complex, int, Complex>("x", "y");
Console.WriteLine(f(new(4, 5), 5));

Output:

(9, 5)

You can pass up to 8 type parameters excluding the one for output.

Extensions include the same set of methods, but for string.

If needed to pass more than 8 parameters, or other types than the ones listed, or custom rules for types, consider declaring your own CompilationProtocol.

CompilationProtocol

Since the way AM compiles an expression is through Linq.Expression, it requires provided rules for constants to be converted into a constant, unary/binary/n-ary nodes to be converted into calls or operators.

That is why AM has class CompilationProtocol which defines a number of rules for every such operation.

Its property ConstantConverter is responsible for converting a given constant expression of type Entity into a Linq.Expression.

BinaryNodeConverter takes two arguments of type Linq.Expression and one argument of type Entity. Although allowed, it is not recommended to rely on the third argument's value. Instead, we encourage to use it as a type holder and switch over it when looking for a good node. For example, that is a fragment of code for this ruleset:

...
return typeHolder switch
{
	Sumf => Expression.Add(left, right),
	Minusf => Expression.Subtract(left, right),
	Mulf => Expression.Multiply(left, right),
	Divf => Expression.Divide(left, right),
...

Same way it is recommended to define UnaryNodeConverter and AnyArgumentConverter (the latter for n-ary nodes).

Now, after you defined your own compilation protocol, you need to pass your delegate and parameters of the final function:

Entity expr = "x + y";
var f = expr.Compile<Func<float, float, float>>(
        new(), typeof(float), 
        new[] { (typeof(float), Var("x")), (typeof(float), Var("y")) }
        );
Console.WriteLine(f(4, 5));

Here we use new() instead of an instance of CompilationProtocol, but instead of it a custom protocol can be passed.

Compilation into FastExpression

It is highly recommend to compile a given expression the way it is described above.

FastExpression is a special type. It contains instructions for every node and works as a stack machine. When compiled into it, it translates a given expression into a number of instructions, which are then sequentially executed.

It does not accept any arguments, as it unconditionally work with Complex. Example:

Entity expr = "x + y";
var f = expr.Compile("x", "y");
Console.WriteLine(f.Call(4, 5));

Output:

(9, 0)

It is not as fast and flexible as Compile<>.

Clone this wiki locally