Running X++ as IL has some huge performance benefits, but you have to be careful. Here is a write up of the changes we went through for the code compare tool in AX 2012 to increase its performance and reduce some IL side effects.

In Dynamics AX 2012 the compare tool has been through two significant changes to improve performance.

New algorithm

The first change was a re-implementation of the algorithm. In AX 2009 (and previous) the algorithm was “home-grown” – in AX 2012 it was changed to use the Longest Common Subsequence algorithm. For texts that were nearly identical this yielded little improvements – but for vastly different texts it became significant. To measure the performance improvement a “typical” text was defined as a text where 10% of all lines contained random text. The new algorithm (orange) could compare two typical texts each with 1100 lines in 10 seconds – the original algorithm (red) could “only” handle 650 lines in 10 seconds.

Running as IL

As the algorithm is pure CPU extensive– there is no database involvement- it could benefit significantly by running the code as IL. Switching over to run X++ code as IL is quiet simple in X++. By running the code as IL we can compare 4300 lines in 10 seconds. (green).

To change the compare code to run as IL, here is what we had to do:

  1. Change the SysCompareText class to run on server tier.
  2. Create a static method that returns a container with results and takes a container as input:

    private
    server static container runCIL(container _inputContainer)
    {
        // Simple variable names used to make correctness obvious.
        str s1, s2;
        boolean b1, b2, b3, b4, b5;

        // Extract parameters from container
        [s1, s2, b1, b2, b3, b4, b5] = _inputContainer;

        // Pass parameters to the compare engine
        return SysCompareText::runInternal(s1, s2, b1, b2, b3, b4, b5);
    }
  3. Call the static method via the runClassMethodIL() API

    XppILExecutePermission perm =

    new XppILExecutePermission();
    perm.assert();
    return runClassMethodIL(classStr(SysCompareText), staticMethodStr(SysCompareText, runCIL),
    [_text1, _text2, _caseSensitive, _suppressWhiteSpace, _lineNumbers, _singleLine, _alternateLines]);

The pitfalls

  1. The generated code may not be what you expect.
    The compare algorithm uses a 2-dimensional array. In X++ that was implemented as an int[] (the offset for the indexer was calculated as x+y*width). However; the resulting CLR code uses an Dictionary<int, int> – which consumes 8x as much memory as an System.Int32[] array. This extensive memory consumption could cause AX to run out of memory, when comparing large files – e.g. comparing two 10,000 lines files would consume >3GB memory. The performance of System.Int32[] should also be better, so it was decided to change the int[] to System.Int32[,] and the benchmark was run again.  Much to my surprise the performance decreased (purple). After a while of looking at IL disassembly the reason was clear. The only way to access the contents of an CLR array in X++ is via the get_Item() method, which is a “slow” virtual method call. If just the X++ compiler converted an int[] to a System.Int32[] instead of Dictionary<int,int> then the lightning fast IL array  indexers could be used (and a factor >30x gained.) Yet, it was decided to use the System.Int32[,] array due to the lower memory consumption – even though it was somewhat slower.
  2. The user may disable IL
    If the user deselects “Execute business operations in CIL” under Tools | Options, then the code will execute as regular “slow” pcode. And in the compare case it will run even slower, as we decided to explicitly use System.Int32[,] instead of int[]. This causes a lot of “slow” interop calls into CLR. (blue)

 

Compare benchmark

Conclusion

By changing to a better algorithm and running the compare algorithm as IL, we have increased the size of texts that can be compared in 10 seconds from 650 lines to 3400 lines – a factor of 5. For small texts the difference is insignificant – the larger the text, the larger the gain.  However; if the user disables IL ,then gain from running IL is gone, and a penalty is paid for the IL specific optimizations implemented. In this case it was an optimization to reduce memory consumption.