Managed or Unmanaged?

It might seem strange in a book about .NET programming that we are asking this question at all. However, the question of whether you should leverage the .NET Framework at all is an important design decision, and given that .NET does put some overhead in both resource use and performance on any application, it is an issue which will have to be addressed in any application for which performance is an important consideration. Indeed, I have another important reason for discussing this: a point I will make quite a lot in this chapter is that architectural decisions and high-level design, such as the choice of algorithm, are often far more important factors in influencing performance than are the low-level 'do I recalculate this value inside the loop?' kinds of questions that many developers associate with performance. And you can't get much higher level than asking whether or not you should be using the .NET Framework in the first place!

In this section, we'll examine the issues that you should consider when deciding whether to use the .NET Framework for an application. Although we will focus on performance, it's not possible to examine performance in isolation in this context, so we will also touch on balancing the conflicting demands of robustness, performance, security, and development time. I can't obviously give any hard advice of the 'you must write it this way' form, since every application is different, and there are so many factors you must weigh up - besides the purely technological aspects we'll consider here, you must balance for example the skill-sets of developers and the prejudices of the managers you have to present your decisions to. However, I will discuss the main issues that you will need to bear in mind. We'll start by reviewing the current and likely future status of the .NET Framework, then go on to assess the issues you are likely to face for broadly new code, before moving on to legacy code.

.NET and the Future

Let's be in no doubt about this: Microsoft regards .NET as the future for programming on the Windows platform, and hopes that in a few years time .NET may be important on other platforms too. Roughly speaking, the aim here is that in five to ten years time, virtually all new code being written will be managed code, with unmanaged code only used for legacy applications and perhaps a few specialist purposes such as device drivers. Whether this actually happens, only time will tell, but we can be sure that Microsoft's research and development efforts over the next few years will become increasingly focused into helping us to write managed rather than unmanaged code.

At present, what I've heard informally from various developers at Microsoft is that managed code will typically run between five and ten percent slower than the equivalent unmanaged code. This is broadly in line with my own experience too, though those figures are only approximate ballpark figures, and will vary considerably between different applications. For example, code that requires a significant amount of interop marshaling is likely to run considerably slower, while code that is computationally intensive may run faster due to the benefits of JIT optimizations. We also need to recognize that this is only version 1.x of the framework - and obviously in getting version 1 of any product out, priority will tend to be focused on actually shipping a working product and making it robust, rather than on fine-tuning its performance. As new versions of .NET emerge, we can expect to see that performance tuning kicking in, and that small performance loss suffered by running managed code under .NET version 1.0 is likely to diminish or go away entirely. A related aspect of this is that over time more and more of the Windows API will become accessible through .NET. At the moment, one complaint among some programmers is the frequency with which they need to make calls through P/Invoke to Windows API functions to access features that are not yet directly available through the framework class library. Each P/Invoke call incurs the usual performance hit, as well as compromising security because it means the code has to be granted access permission to run unmanaged code - which for all practical purposes renders all the extra security checks provided by the .NET Framework useless. (What's the point of, for example, .NET decreeing that an application is not allowed to access the file system if the application can use native code to get around the restriction anyway?) However, given Microsoft's aim of making .NET the universal way of programming, we can expect that as new versions of .NET are released, coverage of the Windows API will become much more extensive so that the need for those P/Invokes will go away. One obvious example of this is the incorporation of managed wrappers into DirectX 9.

There is also an issue about the availability of .NET. Some developers are concerned that managed code will only run if the .NET Framework is installed, which means asking customers to install the framework. Installing the framework is very easy, but you'll have to decide if your customers will accept it. However, be reassured that Microsoft is taking very seriously the issue of getting the .NET Framework placed on as many computers as possible. You can therefore expect that in the very near future, the framework will ship as standard with new PCs and many Microsoft products. For example, Windows .NET Server (which is in beta at the time of writing) will ship with .NET 1.1. In a couple of years' time, requiring the .NET Framework is unlikely to be an issue.

In general, it is still the case that if you want maximum performance for a given algorithm, you will need to write in unmanaged code. However, the advantages of .NET in almost every other area are so great that it may be worth sacrificing some performance. We'll quickly review the main options, before looking in detail at what the balancing considerations are, in view of the present and likely future status of the .NET Framework.

The Options for Managed Development

In performance terms, there is very little to choose between the main Microsoft languages of C#, VB, and C++, although C++ will have a slight edge in many cases, with VB lagging slightly behind the other languages. This is largely because on the one hand, the C++ compiler does perform more optimizations than either the C# or VB ones, while on the other hand, VB, with its looser framework for types and type conversions, does often allow developers to unwittingly write less efficient code that the C# compiler would reject. In some cases, the VB compiler can also emit more verbose IL than you'd get from the equivalent C#. Having said that, these differences are small and can often be eliminated with good coding practices. I'll say more about those differences later in the chapter.

If you have existing code in VB6, the existence of the VS.NET wizard that will convert much of your code to VB.NET is an extremely good reason for migrating to VB.NET. If you need to use unsafe code, you'll need to choose C# or C++, while if you do a large amount of calling legacy unmanaged code, C++ is likely to be a better bet.

The Options for Unmanaged Development

The options here are the same as they were before the days of .NET. We'll comment only on the fact that if performance is that important an issue, you'll almost certainly be coding in C or C++, using ATL, MFC, or the Windows API.

The Options for Interoperability

For mixing unmanaged and managed code, the main three mechanisms are IJW ("It Just Works"), P/Invoke, and COM interoperability:

  • P/Invoke can be used from any .NET language, and has the advantage that the .NET Framework will marshal the .NET types to/from the unmanaged types, at a slight performance loss.

  • It Just Works (IJW) is the mechanism used by C++ for calling unmanaged code without providing a specific DllImport directive. It is not an alternative to P/Invoke, but rather an alternative way of using the P/Invoke services. IJW also allows you to write and embed unmanaged code in the same assembly module as your managed code, saving the need for a separate unmanaged DLL, which can provide a slight performance edge.

  • COM Interoperability. This is the only option if you wish to invoke methods on COM objects. However, it will lead to a larger performance hit than for P/Invoke.

In practice, any real product is likely to consist of a number of modules, which means that communication between them is important, and also that you may find only some modules are being coded as managed code. As far as out-of-process or cross-machine communication between managed modules is concerned, you'll normally find that Remoting using the Tcp channel will give better performance than the other main techniques provided by the framework (ASP.NET web services and Remoting using Http).

I will now summarize some of the main factors you should consider when deciding between managed and unmanaged code for a given module.

Issues to Consider

If you are deciding whether to write a new component for an application as managed or unmanaged code, you should consider the following:

  • Time scale. If the application is only expected to have a lifetime of a year or two, then it probably won't be around long enough to really see .NET at its best. In this case, you might not lose much by sticking with the Windows API. On the other hand, if this component is intended to last in the long term, programming in .NET is likely to be a good investment for the future. (But equally, if the component has a short lifespan, you might feel it's not worth the extra development effort to write it as unmanaged code.)

  • Is it performance-critical compared with development time and other issues? Is this an application where it's really going to matter if it runs five to ten percent slower? For example, if it is basically a user-interface application, then most of its time is going to be spent waiting for user input. In this kind of situation, the ease of programming of .NET, the extra robustness, and the shorter development cycle probably ought to be the prime considerations. A related issue is that, as we've mentioned, that five to ten percent figure is only an approximation - and one that will vary considerably between different applications. If you need to make very frequent calls to Windows API functions, those P/Invokes may add up. In that scenario, your options include:

    • Putting up with the P/Invokes.

    • Using unmanaged code for just a part of your application that needs to call Windows API functions; this allows you to arrange things in such a way that you invoke your unmanaged code less frequently than you would need to invoke the Windows API functions.

    • Writing in managed code in C++, which allows you to access unmanaged code using the IJW mechanism.

  • Are the algorithms complex? There is another side of performance: although a given algorithm might run more slowly under .NET, the availability of the framework class library and the greater ease of programming (for example, the fact that you don't need to worry about memory management) might enable you use a more complex algorithm, or more complex data structures, which leads to higher performance.

Note that these considerations are additional to the obvious ones - such as the developer skill-set.

.NET Performance Benefits

As noted earlier, Microsoft has been making it clear for some time that it believes that eventually .NET code can run faster than the equivalent unmanaged code. Essentially the reason for this is twofold: firstly, the .NET Framework includes some pretty nifty programming that implements some sophisticated algorithms for various boilerplate tasks such as memory management and thread pooling, for which you'd be pretty hard pushed to implement the same algorithms yourself, and partly, the JIT compiler is able to perform additional optimizations not available to traditional compilers, including hardware-specific optimizations. In this section, we will assess in more detail some of the contributions that the .NET Framework can make to performance through aspects of its design.

Garbage Collection

You are probably familiar with the arguments surrounding the relative performance of the managed garbage-collected heap versus the unmanaged C++ heap, but we'll recap them here for completion, before pointing out a couple of subtleties concerning these arguments that aren't so well known.

The main performance benefit claimed for the garbage-collected heap is that the garbage collection process includes tidying the heap so that all objects references occupy one contiguous block. This has two benefits:

  1. Allocating new objects is much quicker because there is no need to search through a linked list to identify a suitable memory location: all that needs to be done is to examine a heap pointer that will tell the CLR where the next free location is.

  2. The objects on the heap will be closer together in memory, and that means that it is likely that fewer page swaps will be necessary in order to access different objects.

These benefits are of course correct and real. Unfortunately, what the Microsoft sales publicity doesn't tell you is the other side to these points.

In the first place, the implicit assumption being made is that identifying free regions in an increasingly scattered heap is the only way that the unmanaged C++ heap can work. It is more correct, however, to say that that is the default behavior for allocating memory in unmanaged C++. However, in C++, if you want to process your memory allocation more efficiently, you can achieve this simply by overriding the C++ new operator for specific data types. Doing this is probably not a sensible option in the most general scenario because of the amount of work involved, but there may be cases in which you as the developer have specialist knowledge of the objects that will need to be allocated - for example, of their sizes or where they are referenced from, which would allow you to code up an override to new without too much effort, and which gives you some of the same advantages as managed garbage collection. It's also worth pointing out that many developers discussing the garbage collection issue will assume that the C++ heap allocator works simply by scanning a linked list looking for memory until it finds a block big enough, and then splits that block. It's quite easy to see how poor performance will become and how hopelessly fragmented the heap would quickly become if you did things this way. Unfortunately, Microsoft has not documented how the heap works internally, but I'd be extremely surprised if it used such a primitive and poorly performing algorithm. Without going into details, there are far more sophisticated algorithms available for managing a C-style heap - something that should be remembered when assessing the benefits of garbage collection.

Secondly, the number of page swaps is ultimately determined by how much virtual memory your application takes up for both its code and its data. Running as managed code could go either way here as far as code size is concerned. On the one hand, a managed application brings in mscorlib.dll and various other DLLs - these DLLs are not small, and will have a big effect on virtual memory requirements. On the other hand, if you are running a number of managed applications that all use the same version of the CLR, then these libraries will only be loaded once and shared across all the applications. The result could be a reduction in total memory requirements compared to the situation if all these apps had been written as unmanaged code. The effect on data size is similarly not easy to predict since there are competing factors; however, it is fair to say that there are some situations in which data size will go up. This is because each reference object in .NET has a sync block index, which takes up a word of memory. The sync block index is there in case you need it for multi-threaded applications, but in many cases it's not needed. If you have a large number of reference objects, the memory taken by the sync block indexes will build up - and this will be especially noticeable if you have many reference objects that don't have many instance fields, so the sync block will take up a relatively large proportion of the size of the objects. On the other hand, the .NET Framework is more sophisticated in how it packs fields in classes and structs together, which may reduce data size. This effect will be most noticeable in types that mix data of varying sizes (such as int32 and bool). Of course, if you understand how word alignment works (and this is documented in the MSDN documentation), then you can achieve the same size efficiency by taking care to define the fields in your unmanaged types in the appropriate order.

Having said all this, however, it is also worth bearing in mind that modern machines tend to have so much memory that page swapping is no longer normally an issue for small or medium-sized applications.

On balance, the issue of page swapping could go either way. If the application is going to be running for a long time, and during that time it will be allocating and de-allocating very large numbers of objects, then the benefits of the defragmented managed heap will tend to increase.

Thirdly, it's worth recalling that the .NET Framework places quite rigid restrictions on the requirements for value and reference types. Managed structs do not support inheritance and give poor performance when used in data structures such as ArrayLists or dictionaries due to boxing issues. Not only that, but the specification of value or reference type is associated with the type: in managed code, it is not possible, for example, to instantiate the same class on the stack sometimes and on the heap at other times. Unmanaged C++ code has no such restrictions. This means that in some cases you may be using referenced types in .NET where in the equivalent unmanaged code you would simply be using data structures that are allocated on the stack anyway - and clearly that may cancel out some of the benefits of the garbage collector.

The JIT Compiler

The JIT compiler is one worry that newcomers to .NET have for performance. If compilation has to be done at run time, isn't that going to hurt performance? If you're reading this book then I guess you've already got enough .NET experience to know that, while it is true that JIT compiling does take time, the JIT compiler is very well optimized, and works very quickly. You'll also be aware that the compiler only compiles those methods that are required, so not all of a large application gets compiled, and that each method is normally only compiled once, but then may be executed thousands of times (though that may not be the case on mobile devices, for example, where memory is scarce: the JIT compiler will be able to discard compiled methods to save memory in that scenario). In the next section on JIT Compiler Optimization, I'll go over the operation of the JIT compiler in more depth.



Advanced  .NET Programming
Advanced .NET Programming
ISBN: 1861006292
EAN: 2147483647
Year: 2002
Pages: 124

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net