Original Author: Emmanuel schanzer
Summary:
This article includes various industrial-level technologies in the hosting world and technical explanations on how they affect performance, involving garbage collection, JIT, remoting, valuetypes, and security.
Overview:
. Net introduced a variety of advanced technologies to improve security, ease of development, high performance. as a developer, it is important to understand any of these technologies and use them efficiently in your code. the advanced tools provided by run time make it easier to create robust applications, but it is the responsibility of R & D personnel to make applications fly faster.
This White Paper will provide you with a pair. net industrial technology, and helps you adjust your code to make it run faster. note that this is not a standard table. now there is a lot of real technical information. the purpose of this article is to focus on performance issues to provide information, and may not be able to answer every technical question you have. if you cannot find the answer to your question, I suggest you read more in the msdn online document library.
I will discuss the following technologies and provide a high-level overview of their purpose and why they affect performance. I will go deep into some underlying technical implementation details and use the sample code to illustrate how to get high performance and high speed from each techonlgy.
Garbage Collection
Thread Pool
The JIT
AppDomains
Security
Remoting
Valuetypes
Garbage Collection
======================================
Basic
Garbage Collection (GC) frees programmers from the common but difficult to debug their wrong tasks by releasing the memory of objects that are no longer in use. generally, the survival path of an object is as follows:
Foo A = new Foo (); // allocate memory for the object and initialize it... a... // use this object to delete a; // clear the object state to clean up // release the memory of this object
In native code, you need to do all this by yourself. ignoring the memory allocation or cleanup phase will lead to unpredictable behavior, and this problem is difficult to debug. If you forget to release the memory, it will lead to memory leakage. in CLR, memory allocation is very close to what we just saw. if we add GC-specific information, we will get something that looks very similar.
Foo A = new Foo (); // allocate memory for the object and initialize it... a... // use this object (this object is strong reachable) A = NULL; // The a object becomes unreachable (out of scope, nulled, etc.) // finally, the recovery of object a also needs to recycle resources of object. // memory is recycled
The steps above are the same in the hosted and unmanaged world until the object can be released. in native code, you need to remember to release the object after it is used. in managed code, once the object becomes unreachable, GC recycles it. of course, if your resource needs to be released to take a very small focus (for example, to close the socket), GC requires your help to handle it correctly. in the code you write, the rule for clearing objects before they are released still applies. You can useDispose ()AndFinalize ()Method. We will talk about the difference between the two later.
If you keep a pointer to a resource, GC may not know whether you want to use the resource in the future. this means that all the rules for explicitly releasing objects you use in native code still apply, but GC will handle everything for you in most cases. if you used to spend one hundred of your time on memory management, now you only need 5% of your time to consider memory management.
The CLR garbage collector is a generational mark-and-compact collector that is marked and organized by generation. it complies with the following principles to achieve outstanding performance. first, short-lived objects are usually small and frequently accessed. GC divides the distribution chart into several subcharts, called generations (generation). Generation allows GC to spend as little time as possible for collection. gen 0 contains young, frequently accessed objects. the scale of these objects approaches the minimum, and it takes about 10 milliseconds to recycle them. because GC can ignore other generation collections during this collection, it can provide higher performance. g1 and G2 are designed for larger, older objects that are not frequently recycled. when G1 is recycled, G0 is also recycled. the collection of G2 is a full collection, and the GC will traverse the entire memory graph. it also intelligently uses the CPU cache, which allows you to adjust the memory subsystem on a CPU. for native memory allocation, this optimization is not easy to obtain, but if it is available, it can help improve the performance of your application.
When does garbage collection occur?
When the memory needs to be allocated, GC checks whether the memory needs to be recycled. GC will check the size of recoverable memory, the remaining memory size, and the size of each generation, and then use a heuristic method to make decisions. until a collection occurs, the object's memory allocation can be as fast as C or C ++, or even faster.
What happened during garbage collection?
Let's take a step-by-step look at the steps that the garbage collector has taken during collection. GC maintains a root list, which points to the heap of GC. if an object is active, a root will point to its position in the heap. objects in the heap can also be referenced by each other. this reachability graph is a GC graph that must be searched to release memory. the sequence of events is as follows:
1. All memory allocation blocks in the managed heap are continuous. GC will be triggered when the remaining block size is insufficient to cope with a request.
2. GC traverses all the pointers after each root and root to generate a list. The objects in the list are not reachable by the previous traversal.
3. Traverse from the root, and every inaccessible object is considered recoverable, and these objects will be marked for future collection.
4. remove objects from the reachability graph so that many objects can be recycled. However, some resources need to be specially processed. When you define an object, you can choose to define it.Dispose ()Method orFinalize ()Methods, or both. We will discuss the differences between them later and discuss when to use them.
5. The last step of recycling is the memory sorting phase. All the objects in use are moved to a continuous memory block, and all pointers and root are updated.
6. by sorting out the active objects and updating the starting address of the available memory, GC maintains the fast continuity of the memory in use. if there is enough space for memory allocation, GC will forward the control to the application. if not, an exception is returned. The type isOutOfMemoryException
Object cleanup
Some objects require special handling before their resources can be returned. A few examples of such resources are files, network sockets, or database connections. simply releasing the memory on the heap isn't going to be enough, since you want these resources closed gracefully. to perform object cleanup, you can writeDispose ()Method,Finalize ()Method, or both.
AFinalize ()Method:
- Is called by the GC
- Is not guaranteed to be called in any order, or at a predictable time
- After being called, frees memory afterNextGC
- Keeps all child objects live until the next GC
ADispose ()Method:
- Is called by the programmer
- Is ordered and scheduled by the programmer
- Returns resources upon completion of the Method
Managed Objects that hold only managed resources don't require these methods. your program will probably use only a few complex resources, and chances are you know what they are and when you need them. if you know both of these things, there's no reason to rely on finalizers, since you can do the cleanup manually. there are several reasons that you want to do this, and they all have to do withFinalizer queue.
In the GC, when an object that has a finalizer is marked collectable, IT and any objects it points to are placed in a special queue. A separate thread walks down this queue, callingFinalize ()Method of each item in the queue.The programmer has no control over this thread, or the order of items placed in the queue.The GC may return control to the program, without having finalized any objects in the queue. those objects may remain in memory, tucked away in queue for a long time. callto finalize are done automatically, and there is no direct performance impact from call itself. however, the non-deterministic model for finalization canDefinitelyHave other indirect consequences:
- In a scenario where you have resources that need to be released at a specific time, you lose control with finalizers. say you have a file open, and it needs to be closed for security reasons. even when you set the object to null, and force a GC immediately, the file will remain open until itsFinalize ()Method is called, and you have no idea when this cocould happen.
- N objects that require disposal in a certain order may not be handled correctly.
- An enormous object and its children may take up far too much memory, require additional collections and hurt performance. These objects may not be collected for a long time.
- A small object to be finalized may have pointers to large resources that cocould be freed at any time. these objects will not be freed until the object to be finalized is taken care of, creating unnecessary memory pressure and forcing frequent collections.
The State distriin Figure 3 has strates the different paths your object can take in terms of finalization or disposal.
As you can see, finalization adds several steps to the object's life time. if you dispose of an object yourself, the object can be collected and the memory returned to you in the next GC. when finalization needs to occur, you have to wait until the actual method gets called. since you are not given any guarantees about when this happens, you can have a lot of memory tied up and be at the mercy of the Finalization queue. this can be extremely problematic if your object is connected to a whole tree of objects, and they all sit in memory until finalization occurs.
Choosing which Garbage Collector to use
The CLR has two different GCS: workstation (mscorwks. DLL) and server (mscorsvr. DLL ). when running in workstation mode, latency is more of a concern than space or efficiency. A server with multiple processors and clients connected over a network can afford some latency, but throughput is now a top priority. rather than shoehorn both of these scenarios into a single GC scheme, Microsoft has included two garbage collectors that are tailored to each situation.
Server GC:
- Multiprocessor (MP) scalable, parallel
- One GC thread per CPU
- Program paused during marking
Workstation GC:
- Minimizes pauses by running concurrently during full collections
The server GC is designed for maximum throughput, and scales with very high performance. memory fragmentation on servers is a much more severe problem than on workstations, making garbage collection an attractive proposition. in a uniprocessor scenario, both collectors work the same way: workstation mode, without concurrent collection. on an MP machine, the workstation GC uses the second processor to run the collection concurrently, minimizing delays while diminishing throughput. the server GC uses multiple heaps and collection threads to Maximize throughput and scale better.
You can choose which GC to use when you host the run time. when you load the run time into a process, you specify what collector to use. loading the API is discussed in. net Framework developer's guide. for an example of a simple program that hosts the run time and selects the server GC, take a look at the appendix.
Myth: Garbage collection is always slower than doing it by hand
Actually, until a collection is called, the GC is a lot faster than doing it by hand in C. this surprises a lot of people, so it's worth some explanation. first of all, notice that finding free space occurs in constant time. since all free space is contiguous, the GC simply follows the pointer and checks to see if there's enough room. in C, a callMalloc ()
Typically
Results In a search of a linked list of free blocks. this can be time consuming, especially if your heap is badly fragmented. to make matters worse, several implementations of the C run time lock the heap during this procedure. once the memory is allocated or used, the list has to be updated. in a garbage-collected environment, allocation is free, and the memory is released during collection. more advanced programmers will reserve large blocks of memory, and handle allocation within that block themselves. the problem with this approach is that memory fragmentation becomes a huge problem for programmers, and it forces them to add a lot of memory-handling logic to their applications. in the end, a garbage collector doesn't add a lot of overhead. allocation is as fast or faster, and compaction is handled automatically-freeing programmers to focus on their applications.
In the future, garbage collectors cocould perform other optimizations that make it even faster. hot Spot identification and better Cache Usage are possible, and can make enormous speed differences. A smarter GC cocould pack pages more efficiently, thereby minimizing the number of page fetches that occur during execution. all of these cocould make a garbage-collected environment faster than doing things by hand.
Some people may wonder why GC isn' t available in other environments, like C or C ++. the answer is types. those versions ages allow casting of pointers to any type, making it extremely difficult to know what a pointer refers. in a managed environment like the CLR, we can guarantee enough about the pointers to make GC possible. the managed world is also the only place where we can safely stop thread execution to perform a GC: In C ++ these operations are either unsafe or very limited.
Tuning for speed
The biggest worry for a program in the managed world isMemory Retention. Some of the problems that you'll find in unmanaged environments are not an issue in the managed World: memory leaks and dangling pointers are not much of a problem here. instead, programmers need to be careful about leaving resources connected when they no longer need them.
The most important heuristic for performance is also the easiest one to learn for programmers who are used to writing native code: keep track of the allocations to make, and free them when you're done. the GC has no way of knowing that you aren't going to use a 20kb string that you built if it's part of an object that's being kept around. suppose you have this object tucked away in a vector somewhere, and you never intend to use that string again. setting the field to null will let the GC collect those 20kb later, even if you still need the object for other purposes. if you don't need the object anymore, make sure you're not keeping references to it. (just like in native code .) for smaller objects, this is less of a problem. any programmer that's familiar with memory management in native code will have no problem here: all the same common sense rules apply. you just don't have to be so paranoid about them.
The second important performance concern deals with object cleanup. as I mentioned earlier, finalization has profound impacts on performance. the most common example is that of a managed handler to an unmanaged Resource: You need to implement some kind of cleanup method, and this is where performance becomes an issue. if you depend on finalization, you open yourself up to the performance problems I listed earlier. something else to keep in mind is that the GC is largely unaware of memory pressure in the native world, so you may be using a ton of unmanaged resources just by keeping a pointer around in the managed heap. A single pointer doesn' t take up a lot of memory, so it cocould be a while before a collection is needed. to get around these performance problems, while still playing it safe when it comes to memory retention, you shoshould pick a design pattern to work with for all the objects that require special cleanup.
The programmer has four options when dealing with object cleanup:
1. implement both
This is the recommended design for object cleanup.This is an object with some mix of unmanaged and managed resources. An example wocould beSystem. Windows. Forms. Control. This has an unmanaged Resource (hwnd) and potentially managed resources (dataconnection, etc .). if you are unsure of when you make use of unmanaged resources, you can open the manifest for your program inILDASM
And check for references to native libraries. Another alternative is to usevadump.exe
To see what resources are loaded along with your program. Both of these may provide you with insight as to what kind of native resources you use.
The pattern below gives users a single recommended way instead of overriding cleanup logic (overrideDispose (bool)). This provides maximum flexibility, as well as catch-all just in caseDispose ()Is never called. The combination of maximum speed and flexibility, as well as the safety-net approach make this the best design to use.
Example:
Public class myclass: idisposable {public void dispose () {dispose (true); GC. suppressfinalizer (this);} protected virtual void dispose (bool disposing) {If (disposing ){...}...} ~ Myclass () {dispose (false );}}
2. ImplementDispose ()Only
This is when an object has only managed resources, and you want to make sure that its cleanup is deterministic. An example of such an object isSystem. Web. UI. Control.
Example:
Public class myclass: idisposable {Public Virtual void dispose (){...}
3. ImplementFinalize ()Only
This is needed in extremely rare situations, and I stronugly recommend against it. The implication ofFinalize ()Only object is that the programmer has no idea when the object is going to be collected, yet is using a resource complex enough to require special cleanup. this situation shoshould never occur in a well-designed project, and if you find yourself in it you shoshould go back and find out what went wrong.
Example:
Public class myclass {...~ Myclass (){...}
4. Implement neither
This is for a managed object that points only to other managed objects that are not disposable nor to be finalized.
Recommendation
The recommendations for dealing with memory management shocould be familiar: release objects when you're done with them, and keep an eye out for leaving pointers to objects. when it comes to object cleanup, implement bothFinalize ()AndDispose ()
Method for objects with unmanaged resources. This will prevent unexpected behavior later, and enforce good programming practices
The downside here is that you force people to have to callDispose (). There is no performance loss here, but some people might find it frustrating to have to think about disposing of their objects. however, I think it's worth the aggravation to use a model that makes sense. besides, this forces people to be more attentive to the objects they allocate, since they can't blindly trust the GC to always take care of them. for programmers coming from a C or C ++ background, forcing a callDispose ()Will probably be beneficial, since it's the kind of thing they are more familiar.
Dispose ()Shocould be supported on objects that hold on to unmanaged resources anywhere in the tree of objects underneath it; however,Finalize ()Need only be placed only on those objects that are specifically holding on to these resources, such as an OS handle or unmanaged memory allocation. I suggest creating small managed objects as "wrappers" for implementingFinalize ()In addition to supportingDispose (),
Which wocould be called by the parent object'sDispose (). Since the parent objects do not have a finalizer, the entire tree of objects will not have ve a collection regardless of whether or notDispose ()Was called.
A good rule of thumb for finalizers is to use them only on the most primitive object thatRequiresFinalization. suppose I have a large managed resource that includes des a database connection: I wocould make it possible for the connection itself to be finalized, but make the rest of the object disposable. that way I can callDispose ()And free the managed portions of the object immediately, without having to wait for the connection to be finalized. Remember: UseFinalize ()OnlyWhereYou have,WhenYou have.
NoteC and C ++ programmers: The Destructor semantic in C # createsFinalizer, Not a disposal method!
Thread Pool
======================================
JIT
======================================
Appdomain
======================================
Security
======================================
Remoting
======================================
Valuetypes
======================================
Original article address:
Performance Considerations for run-time technologies in the. NET Framework
Http://msdn.microsoft.com/en-us/library/ms973838