What You Can Do
At every phase of development, there are several things you can do. With MC++, the design phase is perhaps the most important area, as it will determine how much work you end up doing and how much performance you get in return. When you sit down to write or port an application, you should consider the following things:
- Identify areas where you use multiple inheritance, templates, or deterministic finalization. You will have to get rid of these, or else leave that part of your code in unmanaged space. Think about the cost of redesigning, and identify areas that can be ported.
- Locate performance hot spots, such as deep abstractions or virtual function calls across managed space. These will also require a design decision.
- Look for objects that have been specified as stack-managed. Make sure they can be converted into ValueTypes. Mark the others for conversion to heap-managed objects.
During the coding stage, you should be aware of the operations that are more expensive and the options you have in dealing with them. One of the nicest things about MC++ is that you come to grips with all the performance issues up front, before you start coding: this is helpful in paring down work later on. However, there are still some tweaks you can perform while you code and debug.
Determine which areas make heavy use of floating point arithmetic, multidimensional arrays or library functions. Which of these areas are performance critical? Use profilers to pick the fragments where the overhead is costing you most, and pick which option seems best:
- Keep the whole fragment in unmanaged space.
- Use static casts on the library accesses.
- Try tweaking boxing/unboxing behavior (explained later).
- Code your own structure.
Finally, work to minimize the number of transitions you make. If you have some unmanaged code or an interop call sitting in a loop, make the entire loop unmanaged. That way you'll only pay the transition cost twice, rather than for each iteration of the loop.
Additional Resources
Related topics on performance in the .NET Framework include:
- Performance Considerations of Run-Time Technologies in the .NET Framework
Watch for future articles currently under development, including an overview of design, architectural and coding philosophies, a walkthrough of performance analysis tools in the managed world, and a performance comparison of .NET to other enterprise applications available today.
Appendix: Cost of Virtual Calls and Allocations
Call Type# Calls/sec
ValueType Non-Virtual Call | 809971805.600 |
Class Non-Virtual Call | 268478412.546 |
Class Virtual Call | 109117738.369 |
ValueType Virtual (Obj Method) Call | 3004286.205 |
ValueType Virtual (Overridden Obj Method) Call | 2917140.844 |
Load Type by Newing (Non-Static) | 1434.720 |
Load Type by Newing (Virtual Methods) | 1369.863 |
Note The test machine is a PIII 733Mhz, running Windows 2000 Professional with Service Pack 2.
This chart compares the cost associated with different types of method calls, as well as the cost of instantiating a type that contains virtual methods. The higher the number, the more calls/instantiations-per-second can be performed. While these numbers will certainly vary on different machines and configurations, the relative cost of performing one call over another remains significant.
- ValueType Non-Virtual Call: This