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.
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:
- Change the SysCompareText class to run on server tier.
- 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);
- Call the static method via the runClassMethodIL() API
XppILExecutePermission perm =
return runClassMethodIL(classStr(SysCompareText), staticMethodStr(SysCompareText, runCIL),
[_text1, _text2, _caseSensitive, _suppressWhiteSpace, _lineNumbers, _singleLine, _alternateLines]);
- 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.
- 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)
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.