Pages

Men

rh

7/08/2013

Use AppDomains To Reduce Memory Consumption in .NET applications

Introduction

.NET as a technology has brought about great changes. Besides moving to a managed context, .NET provides developers with the tools to rapidly develop applications. However, there has been one attribute of .NET processes that has been callously overlooked by many: Memory Consumption.

Since .NET processes pull in lots of code from the framework class libraries, each process has a large footprint. When many processes are run, there will be a lot of libraries that are duplicatly loaded in separate address spaces, all taking up valuable system resources. The answer to memory conservation comes in the form of AppDomains in .NET. With AppDomains, multiple applications can run in the same process, thereby sharing the .NET runtime libraries.

Sharing the .NET runtime using AppDomains does come at a slight performance hit, though this will not be noticeable if the application is not using static methods and fields in abundance, yet the improvement in memory consumption is phenomenal. Fully homogenous .NET solutions are not common in industry as of yet, and as a result, the issue of memory has not been brought to the forefront. Yet memory consumption is always a consideration of good software design and this article shows ways to use system resources more efficiently with AppDomains in .NET.

The Benchmark

To benchmark the memory consumption, I created two very simplistic applications. A StayinAlive.exe and aWin32StayinAlive.exe, using an executable and a .NET assembly respectively. Both programs run with a message to the console every 1 second indicating that they are alive and run until the program is shutdown by the user. To illustrate the magnitude of the problem, I ran 5 copies of the .NET version to illustrate the added memory consumption and to prove that there is no efficiency of library usage in running multiple copies of the same program. Consider the diagram below and notice that the Win32 process takes about 700KB of memory whereas its comparable counterpart in .NET takes around 4.5MB of memory. This may not be a problem for simple applications, but when running multiple .NET applications on a system, it can add up very quickly. Our 5 console applications add up to about 22.5MB of memory!

Memory Consumption Before AppDomain Hosting

The code for the Win32 version of StayinAlive is as follows:
int _tmain(int argc, _TCHAR* argv[])
{
 DWORD tid = ::GetCurrentThreadId();
 while( true ){
  printf( "Ah Ah Ah Ah Stayin Alive on Thread %d\n", tid );
  ::Sleep(1000);
 }
 return 0;
}

The code for the .NET managed version of StayinALive is as follows:
[STAThread]
static void Main(string[] args)
{
 string myDomain = AppDomain.CurrentDomain.FriendlyName;
 int    myThrdId = AppDomain.GetCurrentThreadId();
 int    sleepTime= 1000;
  
 if( args.Length > 0 ) 
    sleepTime = Int32.Parse( args[args.Length-1] );
 
 while( true ){
  Console.WriteLine( "Ah Ah Ah Ah Stayin Alive on Thread {1} : {0}", 
            myDomain, myThrdId );
  Thread.Sleep(sleepTime);
 }
 
}

A Bit About AppDomains

Application domains remained a mystery to me for quite a while, but after some general reading in this topic, I came to the following understanding: In .NET, the basic unit of execution is NOT the process, rather it is that of the Application Domain. The only true process is what is called a Runtime Host. The CLR is a DLL which means that, in order to run, it must be hosted in some process: The runtime host. There are basically three runtimes with the .NET framework: Internet Explorer, ASP.NET, and Windows shell. Also note that if you are running .NET 1.0 and 1.1, there will be separate runtime hosts for each of these (i.e. 6 runtime hosts). There are numerous topics regarding application domains such as threads and inter-application communication using remoting that are not in the scope of this article, but I urge you to look up Application Domains in the MSDN documentation.

The point I’m trying to make here is that everything in .NET runs within an application domain. Even though you never create an AppDomain explicitly, the runtime host creates a default domain for you before running your application runs. What makes them even more powerful, is that a single process can have multiple domains running within it. Unlike a thread, each application domain runs isolated from the others with its own address space and memory. So where’s the benefit? What do we get by running multiple applications in the same process versus running multiple processes with single (defaulted) application domains.
Quoting MSDN:
“If an assembly is used by multiple domains in a process, the assembly's code (but not its data) can be shared by all domains referencing the assembly. This reduces the amount of memory used at run time.”
But….....
“The performance of a domain-neutral assembly is slower if that assembly contains static data or static methods that are accessed frequently.”
So, would I run multiple data processing applications that used static methods in the same process, just to save memory? Of course not, but I certainly would consider it if I had some high latency applications that, just like my dog Debug, slept around for a good part of the day.

The Solution

To illustrate the power of AppDomains and the actual improvement in memory consumption, I created an ApplicationHost process. This process will load multiple .NET executables into multiple application domains, in the same process, so we can see just how much memory we save.

First, I created a class HostedApp that represents an application being hosted in my process, as follows:
public class HostedApp
{
internal string domainName;
internal string processName;
internal int    sleepTime=1000;
internal AppDomain ad;
internal Thread appThrd;
 
public HostedApp( string domain, string process ){
 this.domainName = domain;
 this.processName = process;
 ad = AppDomain.CreateDomain( domain );
}
 
public void Run(){
 appThrd = new Thread( new ThreadStart( this.RunApp ) );
 appThrd.Start();
}
 
private void RunApp(){
 string [] args = new string[]{ sleepTime.ToString() };
 ad.ExecuteAssembly( this.processName, null, args );
}
 
}

Each HostedApp instance will create a new AppDomain to run the hosted .NET process. The RunApp() method is privately scoped and is matched to the delegate for a ThreadStart procedure. The Run() method is called on the instance to start the hosted process in its own Thread and AppDomain.

The executing method I implemented to test this class is as follows:
[STAThread]
static void Main(string[] args)
{
 const int NUMAPPS = 5;
 HostedApp[] apps = new HostedApp[NUMAPPS];

 string appName = "StayinAlive.exe";
 for( int i=0; i<NUMAPPS; i ++ ){
  apps[i] = new HostedApp( ("Application"+(i+1)), appName );
  apps[i].Run();
 }
}

And the verdict is ........

Sample screenshot

That’s right folks … 7.6MB for 5 processes + the hosting application. This is an approximate savings of 15MB of memory to get the same job done.

Issues

When you run the above code, you will notice one thing. Since all hosted applications are run in the same process (Windows shell), they will share the same console. Hence, if you have console applications that require user interaction from the console, this can get messy. 

There is hope though. When running Windows applications, such as forms, the forms are all hosted in separate threads and will run independently. In the source code provided, I have also included a simple StayinAliveForm.exe program that launches a form (instead of a console) that displays the same message in aLabel control with the current time. For extra credit, you can change the hosted application from StayinAlive.exe toStayinAliveForm.exe and watch the program run with 5 independent and fully functional forms. Of course, your savings in memory will be comparatively more due to the larger footprint for Windows applications.

Also, note that the .NET runtime is not dumb in the least bit. A lot of this memory will actually get paged out eventually, but the fact is that it still is committed memory and can be paged back to physical memory any time.

Conclusion

Application domains are very powerful, yet they can get very complex very quickly if you dive into advanced features such as moving data between domains and communicating with domains hosted on remote machines. If you have huge assemblies of your own, for example, rich user interface component libraries, that are shared by multiple processes in your product, you may want to consider using App Domains to improve memory consumption. Again, I refer you (and myself) to the MSDN documentation on this to further your (and my) knowledge about application domains. Good luck and thanks for the memory!

Source collected from Codeporject.com

No comments :

Post a Comment