A site devoted to discussing techniques that promote quality and ethical practices in software development.

Monday, July 9, 2007

Delphi.Net assembly loading inefficiency

Further to my reporting on the poor performance and scalability of VCL.Net, Delphi.Net also has assembly loading inefficiency that is opposite to what can be produced with .Net languages. This message is to describe this cost and how one could use design pattern to ease the pain - not totally removing it.

Consider the following UML diagram describing a very simplified Delphi program:

This is a very common approach. So in the unit that contains TForm2, you will have a uses statement mentioning the unit for MyClass3 and so on.

The following sequence diagram describes what happens when one double clicks on TestClient.exe to launch it from Windows Explorer:
In other words, as soon as the DPR is loaded, it starts to load not only the MyDemo3.dll contains the class that TForm2 requires but it transverses right down to the lowest level, loading each DLL as it goes. At each loading, it executes any code that is in the initialization section of the unit. Delphi.Net compiler translates each unit into a static class whose name is the name of the unit protected inside a namespace whose name is formed with the unit name plus a suffix '.Units'. Any code defined in the initialization section is placed in the static constructor of this class.

This kind of loading is opposite to .Net programs developed with other languages, such as VB.Net or C#. Programs produced by those language only load the DLLs until code in those assemblies are executed and the JIT compiler only compiles what is needed.

In a non-trivial solution, Delphi.Net will produce a solution that literally loads up all the DLL, jits the classes that are in the unit namespace, run their static constructor code. In other words an awfully inefficient loading engine and solution and the time taken to carry out all these actions can become very significant portion of the start up cost.

This is the code in the static constructor of MyClass3.Units.MyClass3:
static MyClass3()
{
RuntimeHelpers.RunClassConstructor(System);
RuntimeHelpers.RunClassConstructor((RuntimeTypeHandle) MyClass2);
RuntimeHelpers.RunClassConstructor((RuntimeTypeHandle) MyClass1);
MyClass3();
}
This is the source of where all the loading begins, the DPR code, TestClient.Units.TestClient's static constructor:
static TestClient()
{
RuntimeHelpers.RunClassConstructor(System);
RuntimeHelpers.RunClassConstructor(SysUtils);
RuntimeHelpers.RunClassConstructor(Forms);
RuntimeHelpers.RunClassConstructor((RuntimeTypeHandle) MainForm);
}
The last line kicks off the loading. When the MainForm.Units.MainForm's static constructor is executed, it loads that for MyClass3, and so on.

Here is System.Diagnostic.Debug.WriteLine() traces that I've placed in each unit's initialization section.
[5988] MyClass2.initialization
[5988] MyClass1.initialization
[5988] TMyClass3.initialization
[5988] TForm2.initialization
So beware when you are migrating code from VCL.Win32 to VCL.Net. You need to do extra work in VCL.Net to make your program runs efficiently and this can be a signficant cost or at least more than what CodeGear leads to believe.

While this 'load-just-in-case' attitude is always present in Delphi.Win32 but because it does not require jitting and all those processes, the cost in Win32 is not so significant. Nevertheless, it does defeat DLL delay loading that has been around in Win32 for years. Inefficiency nevertheless!

So how to protect yourself from this wastage? The idea is to apply the Dependency Injection or Inversion principle to loosen the coupling. Using this principle, the naive implementation becomes something like this:
Many who are familiar with COM would recognize this pattern is something they have been using for years. The Publisher.dll is nothing more than the type library carrying DLL.

If you run TestClient.exe up, all you see being loaded is Publisher.dll. You will not see the rest of the DLLs being loaded until you need to create MyClass3. To avoid binding to the definition of MyClass3, you should use System.Activator.CreateInstance() to create MyClass3 and then to use IDemo3 to invoke methods. This is exactly the same programming model that is being used in COM, just like what Don Box mentioned in relation to answering the question "Is COM dead?". This is what he said:
COM is many things to many people. To me, COM is a programming model based on integrating components based on type. Period. This was COM's primary contribution to the field of component software, and that contribution has changed the way millions of programmers build systems today.
Now, if Delphi.Net/Pascal has assembly visible type, which it does not have, MyClass3 can be marked in such a manner further restricting user from directly binding to MyClass3 resulting in the inefficiency that we are trying to minimize.

No comments:

Blog Archive