Optimize Performance Through 100% Managed Code

Introduction

What is managed code and why is it important to use 100% managed code in .NET applications? Managed code is compiled for the .NET run-time environment. It runs in the Common Language Runtime (CLR), which is the heart of the .NET Framework. The CLR provides services such as security, memory management, and cross-language integration. (3) Managed applications written to take advantage of the features of the CLR perform more efficiently and safely, and take better advantage of developers' existing expertise in languages that support the .NET Framework. Unmanaged code includes all code written before the .NET Framework was introduced-this includes code written to use COM, native Win32, and Visual Basic 6. Because it does not run inside the .NET environment, unmanaged code cannot make use of any .NET managed facilities. (1)

Advantages of Using Managed Code

Managed code runs entirely "inside the sandbox," meaning that it makes no calls outside of the .NET Framework. That's why managed code gets the maximum benefit from the features of the .NET Framework, and why applications built with managed code perform more safely and efficiently.

Performance

The CLR was designed from the start to provide good performance. By using 100% managed code, you can take advantage of the numerous built-in services of the CLR to enhance the performance of your managed application. Because of the runtime services and checks that the CLR performs, applications do not need to include separate versions of these services. (9) And by using 100% managed code, you eliminate the performance costs associated with calling unmanaged code.

Just-In-Time compiler
The CLR never executes Common Intermediate Language (CIL) directly. Instead, the Just-In-Time (JIT) compiler translates CIL into optimized x86 native instructions. (9) That's why using managed code lets your software run in different environments safely and efficiently. In addition, using machine language lets you take full advantage of the features of the processor the application is running on. For example, when the JIT encounters an Intel processor, the code produced takes advantage of hyper-threading technology. (5)

Another advantage of the JIT is improved performance. The JIT learns when the code does multiple iterations. The runtime is designed to be able to retune the JIT compiled code as your program runs. (2)

NGEN utility
NGEN.exe is a .NET utility that pre-compiles the application at install time. Pre-compiling improves start-up performance for managed code, especially when the application uses Windows Forms. Methods are JITed when they are first used, incurring a larger startup penalty if the application calls many methods during start-up. Because Windows Forms uses many shared libraries in the operating system, pre compiling Windows Forms applications usually improves performance. (12)

Pre compiling also makes sure that the application is optimized for the machine on which it is being installed.

Maintaining a 100% managed code environment
Only when your .NET application uses components that are built using 100% managed code do you receive the full benefits of the .NET environment.

For example, when accessing data through ADO.NET, using wire protocol .NET data providers lets you preserve your managed code environment, because they do not make calls to native Win32 APIs and Client pieces.

The performance advantages of the managed code environment are lost when you (or the components you are using) call unmanaged code. The CLR makes additional checks on calls to the unmanaged or native code, which impacts performance.

Unmanaged code includes the database client pieces that some .NET data providers. Examples of .NET data providers that use managed code are IBM's DB2 data provider and the Oracle Data Provider for .NET (ODP.NET), must use to access a database. Microsoft's SQL Server, Oracle, and OLE DB data providers, and ODBC.NET, also make calls to native Win32 database client pieces or other unmanaged code.

Automatic memory management


Automatic memory management is one of the most significant features of managed code. The CLR garbage collector automatically frees allocated objects when there are no longer any outstanding references to them. The developer does not need to explicitly free memory assigned to an object, which goes a long way toward reducing the amount of time spent debugging memory leaks. (10) There can be no memory leaks in 100% managed code.

Automatic lifetime control of objects


Another significant advantage of using managed code is that the CLR provides automatic lifetime management of components and modules. Lifetime control includes:

Garbage collection, which frees and compacts memory.

Scalability features, such as thread pooling and the ability to use a non-persistent connection with a dataset.

Support for side-by-side versions.

Garbage collection
When an object is created with the new operator, the runtime allocates memory from the managed heap. Periodically, the CLR garbage collector checks the heap and automatically disposes of any objects that are no longer being used by the application, reclaiming their memory.

The garbage collector also compacts the released memory, reducing fragmentation. (4) This function is particularly important when the application runs on large memory servers. Changing the application to use smaller objects can help to improve the effectiveness of the garbage collector. Similarly, because each DLL is assigned a 64-bit chunk of memory, combining small DLLs avoids inefficient use of memory.

Because the garbage collector automatically closes unused objects, memory leaks are not possible in an application that uses 100% managed code.

Scalability features
Thread pooling lets you make much more efficient use of multiple threads and is an important scalability feature of using managed code. The .NET Framework comes with built-in support for creating threads and using the system-provided thread pool. In particular, the ThreadPool class under the System.Threading namespace provides static methods for submitting requests to the thread pool. In managed code, if one of the threads becomes idle, the thread pool injects another worker thread into the multithread apartment to keep all the processors busy.

The standard ThreadPool methods capture the caller's stack and merge it into the stack of the thread-pool thread when the thread-pool thread starts to execute a task. If you are using unmanaged code, the entire stack will be checked, which incurs a performance cost. In some cases, you can eliminate the stack checking with the Unsafe methods ThreadPool.UnsafeQueueUserWorkItem and ThreadPool.UnsafeRegisterWaitForSingleObject, which provide better performance. However, using the Unsafe method calls does not provide complete safety. (8)

Further adding to scalability is the ability to use a non-persistent connection with a dataset, which is a cache of the records retrieved from the database. The dataset keeps track of the state of the data and stores the data as pure XML. Database connections are opened and closed only as needed to retrieve data into the dataset, or to return updated data. (7)

Versioning
Versioning essentially eliminates "DLL hell." When you define an assembly as strongly named, the .NET executable will be executed with the same DLL with which it was built. This means that you can have side-by-side versions of a DLL, allowing you to manage shared components. Versioning ensures that each time an application starts up, it checks its shared files. If a file has changed and the changes are incompatible, the application can ask the runtime for a compatible version.

However, when an application calls unmanaged DLLs, you can end up back in "DLL hell." For example, Oracle's ODP.NET data provider calls the unmanaged Oracle Client pieces, which are specific to a particular version of Oracle. You could install two versions of this unmanaged data provider, for example, one for Oracle9i and one for the upcoming Oracle10G, but you would have a conflict, because each data provider will require a particular version of the clients. Since the clients are native Win32 DLLs, you cannot easily have side-by-side versions running on the same machine. Only with native wire protocol data providers built from 100% managed code can you install side-by-side versions with no configuration required by the end-user.

Checks by the .NET runtime


The .NET runtime automatically performs numerous checks to ensure that code is written correctly. Because these checks prevent a large number of bugs from ever happening, developer productivity is improved and the application quality is better. In addition, these checks thwart system attacks such as the exploitation of buffer overruns.

The CLR checks for type safety to ensure that applications always access allocated objects in appropriate ways. In other words, if a method input parameter is declared as accepting a 4-byte value, the common language runtime will detect and trap attempts to access the parameter as an 8-byte value. Type safety also means that execution flow will only transfer to known method entry points. There is no way to construct an arbitrary reference to a memory location and cause code at that location to begin execution.

In addition, array indexes are checked to be sure they are in the range of the array. For example, if an object occupies 10 bytes in memory, the application can't change the object so that it will allow more than 10 bytes to be read. (11)

Cross-language integration


You can write .NET applications in many different languages, such as C, C++, Visual Basic, COBOL, Fortran, Perl, Pascal, Jscript, Lisp, Python, Smalltalk, and others. Programmers can use the languages that they are most proficient with to develop portions of an application.

All CLR-compliant languages compile to Common Intermediate Language (CIL). CIL is the key to making the .NET application platform-neutral and hardware independent.

In addition, programmers can choose specific languages for specific tasks within the same application. Some languages are stronger than others for particular tasks, and programmers can choose the language best suited for the task. The originating language doesn't matter, because all .NET-compliant compilers produce CIL.

Platform-neutrality


A managed .NET application can execute on any Windows platform that supports the .NET common language runtime. Currently, these platforms are Windows 98, Windows 2000, Windows ME, Windows NT, Windows XP, and Windows Server 2003 (32-bit). Support for the .NET Framework and Common Language Runtime on Windows Server 2003 (64-bit) is planned for an upcoming release.

In addition, with the Microsoft Mobile Internet Toolkit, developers can create a .NET compliant, mobile Web application that can be adapted to the display of multiple wireless devices. (6)

Security

Managed code does not have direct access to memory, machine registers, or pointers. The .NET Framework security enforces security restrictions on managed code that protects the code and data from being misused or damaged by other code. An administrator can define a security policy to grant or revoke permissions on an enterprise, a machine, an assembly, or a user level. For these reasons, applications that use managed code are much safer.

Code access security
Code access security lets the administrator specify which operations a piece of code can perform, stopping inappropriate behavior before it can start. You can configure a complex set of rules that specify whether a code group can both read and write files, demand that the code's callers have specific permissions, allow only callers from a particular organization or site to call the code, grant permissions to each assembly that is loaded, and compare the granted permissions of every caller on the call stack at runtime to the permissions that callers must have and which resources the code can access. (6)

The access privileges an administrator assigns depend in part on where the application is running. For example, by default, an application that runs from the local computer has a higher level of trust and more privileges, such as accessing the file system, than an application that is running from the Internet.

Calling unmanaged code bypasses the .NET CLR security. An application that calls unmanaged code doesn't necessarily have a security problem, it simply has an open door to the possibility of problems due to the functionality of the unmanaged code that has direct access to memory or machine registers, or uses pointers. Once the unmanaged code is being executed, the CLR can no longer check it.

Avoiding buffer overruns
One common type of attack attempts to make API methods operate out of specification, causing a buffer overrun. This attack typically passes unexpected parameters, such as an out-of-range index or offset value. Managed code avoids the buffer overruns that trigger so many security snafus.

Buffer overruns usually occur in programs written in languages such as C or C++, which do not check array bounds and type safety. If an application does not check the validity of the destination buffer size and other parameters, the copied data might overrun the buffer, overwriting the data in adjacent addresses.

Buffer overruns are theoretically impossible in managed code.

Summary

Using 100% managed code gives you solid performance, improved security, and fewer bugs. The CLR provides memory management and lifetime control of objects, including scalability features and versioning. When you call unmanaged code, you lose many of the valuable benefits of the .NET environment.


References

1. Gentile, Sam. "Intro to Managed C++, Part 2: Mixing Managed and Unmanaged Code." The O'Reilly Network. http://www.ondotnet.com/lpt/a/3226 <08/20/2003>

2. Gray, Jan. "Writing Faster Managed Code: Know What Things Cost." MSDN Library. http://msdn.microsoft.com/library/?url=/library/en-us/dndotnet/html/fastmanagedcode.asp <08/20/2003>

3. Gregory, Kate. "Managed, Unmanaged, Native: What Kind of Code Is This?" http://www.developer.com/net/cplus/print.php/2197621 <08/20/2003>

4. Mariani, Rico. "Garbage Collector Basics and Performance Hints." MSDN Library. April 2003. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/dotnetgcbasics.asp <08/20/2003>

5. McNaughton, Allan. "Boosting the Performance of Microsoft .NET." MSDN Library. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/optimaldotnet.asp <08/20/2003>

6. Microsoft Corporation. "Deployment Guide for the Microsoft Mobile Internet Toolkit." http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnmitta/html/deploymobilwebapp.asp?frame=true <08/20/2003>

7. Microsoft. "The Windows Server 2003 Application Environment." MSDN Library. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetserv/html/windowsnetserver.asp <08/20/2003>

8. Microsoft. .NET Framework Developer's Guide. Microsoft .NET Framework SDK1.0. 2001.

9. Noriskin, Gregor. "Writing High-Performance Managed Applications: A Primer." MSDN Library. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/optimaldotnet.asp <08/04/2003>

10. Platt, David S. Introducing Microsoft .NET. Microsoft Press. Redmond, WA. 2001.

11. Richter, Jeffrey. "Microsoft .NET Framework Delivers the Platform for an Integrated, Service-Oriented Web." MSDN Magazine. http://msdn.microsoft.com/msdnmag/issues/0900/Framework/default.aspx <08/20/2003>

12. Schanzer, Emmanuel. "Performance Tips and Tricks in .NET Applications." MSDN Library. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dndotnet/html/dotnetperftips.asp <08/20/2003>
