Prexonite Script

a .NET hosted scripting language with a focus on meta-programming and embedded DSLs

View the Project on GitHub SealedSun/prx

CIL compilation hints and their effects

Posted on 2008-02-19

In the last article, I presented the Prexonite CIL compiler and the huge performance improvements it comes with. Unfortunately, the compiled code has to be dynamically typed as the CIL compiler does not perform any data flow analysis and can therefore not possibly infer the correct types. It does not even create its own representation of the byte code program. However, to say Prexonite Script (the language) is strictly dynamically typed is actually wrong, as the Prexonite compiler emits code for which the types and even the method overloads are known at compile time. It's just that the virtual machine does not provide a way to take advantage of this knowledge.

One such example is the foreach loop, a construct that consists of

and gets transformed into

var enum = $list$.GetEnumerator~IEnumerator;
while(enum.MoveNext()) {
  $element$ = enum.Current;
  $block$
}

This pseudo code represents what is emitted by the Prexonite compiler for foreach AST nodes. It is clear that enum has to be at least of type IEnumerator in all cases. This information could enable the CIL compiler to statically type the variable enum, turning two late-bound calls (MoveNext and Current) into virtual calls.

CIL compilation hints

CIL compilation hints are basically a reverse mapping from byte code to AST nodes, reduced to the minimal amount of information required by the CIL compiler to emit optimized code. It is not that the whole AST is now encoded in the Meta tables of functions. Only nodes, for which the CIL compiler could generate better code, emit CIL hints. One example is the foreach node, which emits the name of the enumerator variable and the addresses of the late-bound calls to be optimized. The CIL compiler decodes this information and performs the necessary steps. The enumerator variable for instance will be of type IEnumerator<PValue> and won't be initialized ahead of time.

Impact on performance

The two main paradigms to interact with sequences in Prexonite are the combination of coroutines (sequence operators like where and map) and the use of foreach loops. While coroutines have the advantage of compose ability and deferred execution, foreach loops are usually faster. Again, I used micro benchmarks to demonstrate the impact on performance.

For practical reasons the number of iterations depends on the size of the set to iterate over in the inner loop. N = 200'000 makes the basis. With sets of 10 and 100 elements, N is reduced to 20'000 and 2'000 respectively.

Iterations over a set (Measurements)

What you are seeing here are performance improvements of 950 to 3'400%, but keep in mind that those are very specialized micro benchmarks and that unless your program exclusively consists of mindless foreach loops, you will not likely experience such speed-ups. Nonetheless, iteration over lists is a very important aspect of many of the programs I have written in Prexonite Script.