When to use Managed Code in Dynamics AX
At a Dynamics AX conference in summer 2011, I was asked for guidance on when to use C# compiled to .NET Framework CIL, instead of traditional interpreted X++ compiled to p-code, in my Dynamics AX application. I will describe some deciding factors for you to consider. Some material presented herein is applies only to AX 2012, but most of it also applies to AX 2009.
If you employ developer talent that knows managed code and Visual Studio well, it makes sense to leverage that asset. Despite its benefits, this managed (C#, VB.NET etc.) alternative does not change the fact that the main problem people have when they ramp up on business logic development projects in AX is understanding the huge code base and its many complex interactions.
In general we do not try to compete with .NET functionality: Rather we leverage .NET and implement complimentary features for X++.
The following table describes some scenarios where the C# alternative tends to be a good choice.
Scenario favoring C# |
Explanation |
Integration with 3rd party tools. |
In a heterogeneous environment, it might make good sense to use C# instead of interpreted X++. The .NET environment is likely to have better support for emerging technologies than the X++ stack has. For instance, consider managed code if you interact with message queues or WCF. |
Web services or messaging. |
This is a common scenario for having X++ call managed code. It is easy to create a web reference to a project in Visual Studio. Visual Studio creates all the proxies and other plumbing that are needed to call the web service, which can then be consumed from X++. |
Complex algorithms. |
Processes that burden the computer’s CPU with intensive calculations may run more efficiently as managed code than as interpreted X++. The performance improvement by using managed code is sometimes dramatic. |
Deep class hierarchies. |
C# achieves major performance improvements when the logic involves many calls to virtual methods, or involves numerous assignment statements. |
The following table describes some scenarios where traditional interpreted X++ tends to be a good choice.
Scenario favoring interpreted X++ |
Explanation |
Database access. |
The X++ language shines with the ease of use it provides for accessing data from AX tables. Further, applications that frequently access data do not run much faster as .NET CIL than they do as interpreted X++. |
Many transitions across boundaries. |
C# might not be the best choice when using C# would result in frequent crossing of the managed / unmanaged boundary. The calls made across this boundary into managed assemblies use .NET reflection, which is orders of magnitude slower than direct managed calls. In other words, scenarios that are chatty across the managed / unmanaged boundary are not the best candidates for C#. |
To demonstrate the faster performance offered by C# compiled to .NET CIL, X++ code for the “towers of Hanoi” is provided below. This algorithm features lots of recursive calls. The number of calls is an exponential function of the number of “discs” that is passed to the method. If there are n discs, the number of calls required is 2n -1, the so-called Mersenne numbers.
In AX 2012 we have the new luxury of being able to execute X++ as CIL code directly, which makes it easier to prove the performance benefit of CIL. However, the conclusions are the same regardless of whether the CIL is derived from X++ or C#.
Below is the contents of an .XPO file that you can import into Microsoft Dynamics AX 2012 (not into AX 2009). The .XPO contains the following two methods:
RunInCIL
RunInXppInterpreter
Create a simple AX job (under AOT > Jobs) where you call the two methods with the same parameter value. Feel free to experiment by passing in values from 17 to 22. After each run you should compare the time spent evaluating in p-code and in CIL. You will see that the speed advantage of CIL over p-code can grow to any order of magnitude as you increase the number of discs to move.
Note that you may see an overhead from the JIT’ing and from loading the assembly containing the code into the current app domain the first time the code is called. Just call it again to get more meaningful numbers.
Exportfile for AOT version 1.0 or later
Formatversion: 1
***Element: CLS
; Microsoft Dynamics AX Class: TowersOfHanoi unloaded
; --------------------------------------------------------------------------------
CLSVERSION 1
CLASS #TowersOfHanoi
PROPERTIES
Name #TowersOfHanoi
Origin #{A4154B6B-C58B-42AC-8335-5BCED29FF8E3}
ENDPROPERTIES
METHODS
SOURCE #classDeclaration
#class TowersOfHanoi
#{
#}
ENDSOURCE
SOURCE #Hanoi
#public server static int Hanoi(int n, int fromPole, int toPole, int intermediatePole)
#{
# if (n == 0)
# {
# return 0;
# }
# else
# {
# return 1 + TowersOfHanoi::Hanoi(n-1, intermediatePole, fromPole, toPole) +
# TowersOfHanoi::Hanoi(n-1, toPole, intermediatePole, fromPole);
# }
#}
ENDSOURCE
SOURCE #HanoiContainer
#private server static container HanoiContainer(container args)
#{
# int discs, f,t,i;
# [discs, f,t,i] = args;
# return [TowersOfHanoi::Hanoi(discs, f,t,i)];
#}
ENDSOURCE
SOURCE #Main
#public static void Main(Args a)
#{
# int discs = 21;
# int endXpp, startXpp;
# int endCIL, startCIL;
# int result1, result2;
#
# startXpp = WinAPIServer::getTickCount();
# result1 = TowersOfHanoi::RunInXppInterpreter(discs);
# endXpp = WinAPIServer::getTickCount();
#
# startCIL = WinAPIServer::getTickCount();
# result2 = TowersOfHanoi::RunInCIL(discs);
# endCIL = WinAPIServer::getTickCount();
#
# info (strFmt('%1 in (Ax: %2, CIL:%3)', result1, endXpp-startXpp, endCIL-startCIL));
# pause;
#}
ENDSOURCE
SOURCE #RunInCIL
#public static server int RunInCIL(int i)
#{
# container c;
#
# new XppILExecutePermission().assert();
#
# c = runClassMethodIL(
# classStr(TowersOfHanoi), staticMethodStr(TowersOfHanoi, HanoiContainer),
# [i, 1,2,3]);
#
# return conPeek(c, 1);
#}
ENDSOURCE
SOURCE #RunInXppInterpreter
#public server static int RunInXppInterpreter(int i)
#{
# return TowersOfHanoi::Hanoi(i, 1,2,3);
#}
ENDSOURCE
ENDMETHODS
ENDCLASS
***Element: END