Reduce distribution Rates
This is almost without explanation, reducing the amount of memory used, naturally reducing the pressure on GC recovery, while reducing the amount of memory fragmentation and CPU usage. You can do this in a few ways, but it may conflict with other designs.
You need to carefully examine each of the objects as you design them and ask yourself:
Do I really need this object?
Is this field what I need?
Can I reduce the size of the array?
Can I reduce the size of primitives (replace Int64 with Int32, others)?
Are these objects used only in rare cases, or only when initialized?
Can I convert some classes to structs so that they are allocated on the stack or become part of an object?
Do I allocate a lot of memory, but actually use only a small part of it?
Can I get the relevant data from somewhere else?
Little story: In a function that responds to requests on the server side, we find that in a single request, we allocate some memory that is larger than the memory segment. This causes each request to trigger a full GC, because the CLR requires that all 0 generations of objects be in one memory segment, the current allocated memory segment is full, a new memory segment is created, and the original memory segment is recycled for 2 generations. This is not a good implementation, because we have no other way than to reduce memory allocation.
The most important rule
There is a basic rule for high-performance programming for garbage collection, which is actually a guideline for code design.
The object to be collected is either in 0 generations or does not exist
Collect objects in Gen 0 or not at all.
The difference is that you want an object to have a very short life cycle, never touch it in a GC, or, if you can't do that, they should go to 2 generations, as fast as possible, stay there forever, and never be recycled. This means that you always keep a reference to long life cycle objects. In general, it also means that objects can be reused, especially objects in the large object heap.
GC recovery for each higher generation is more time-consuming than the previous generation. If you want to keep many 0,1 generations and a small number of 2-generation objects. Even if you turn on the background GC for 2 generations to do the recycling, it consumes quite a CPU, and you might prefer to consume this part of the CPU to the application, not the GC.
Note you may have heard a statement that every 10 times 0 generations of recycling will produce a 1-generation recovery, and every 10 times 1 generations of recycling will produce 1 generations of recycling. This is not true, but you have to understand that you have to make as many quick 0-generation recoveries as possible, as well as a small amount of 2-generation recycling.
You'd better avoid 1-generation recycling, mainly because the objects that have been upgraded from 0 to 1 generations will be transferred to the 2 generation. The 1 generation is a buffer of objects entering the 2 generation.
Ideally, each object you assign should end the life cycle before the next 0-generation collection. You can measure the time interval of two GC and compare it to the life cycle length of the object in your application. Information about how to use the tool to measure the life cycle can be seen at the end of this chapter.
You may not be used to thinking like this, but the rules cut into every aspect of the application, and you need to think about it often and make a fundamental change in mindset so that the most important rule can be achieved.
Shorten the life cycle of an object
The shorter the scope of an object, the smaller the chance that it will be promoted to the next generation when the next GC appears. In general, do not create an object until you need it.
At the same time, when the cost of object creation is so high, the exception can be created at an earlier time, without disturbing other processing logic.
In addition, you want to make sure that the object exits the scope as early as possible. For local variables, you can end their life cycle after the last use, even before the method ends. You can include the code with {}, which will not affect your operation, but the compiler will assume that the object in this range has completed his life cycle and is no longer being used. If you need to invoke the method of the object, minimize the first and last time intervals so that the GC reclaims objects as early as possible.
If an object associates (references) some objects that are held for long periods of time, you need to disassociate them from their referential relationships. You may have more NULL checks (null judgments), which can make your code more complex. It also creates tensions with efficiency on the available state of the object (always has the full available), especially when debugging.
One way to do this is to convert the objects that will be emptied to another way, such as log messages, so that the relevant information can be queried when debugging later.
Another option is to add configurable options to the code (without releasing the relationship between objects): Run the program (or run a specific part of the program, such as a specific request), in which the object reference relationship is not dismissed, but as far as possible the object is kept handy for debugging.
Reduce the depth of the object hierarchy
As described at the beginning of this chapter, the GC iterates through the object's referential relationships as it recycles. In server GC mode, the GC runs multithreaded, but if a thread needs to handle a deep object hierarchy, all threads that have already been processed need to wait for the thread to finish processing before exiting. In future versions of the CLR, you don't have to pay much attention to this issue, and the GC performs load balancing with a better tagging algorithm when multithreaded. But if you have a very deep level of object, this is a matter of concern.
Reduce the references between objects
This is related to the depth of the previous section, but there are some other factors.
An object that refers to many objects (arrays, list bars) will spend a lot of time traversing the object. is a problem that the GC causes for a long time because it has a complex diagram.
Another problem is that if you cannot easily determine how many reference relationships an object has, then you cannot accurately predict the life cycle of an object. Reducing this complexity is quite necessary, not only to make the code more robust, but also to facilitate debugging and get better performance.
Also, it is important to note that references between different generations of objects can also lead to inefficient GC, especially the old object's reference to the new object. For example, if a 2-generation object has a reference relationship in a 0-generation object, then each 0-generation GC will need to be scanned for some 2-generation objects to see if they remain on the reference of the 0-generation object. While this is not a complete GC, it is still not working and you should try to avoid this situation.
Avoid pinning objects (pinning)
Pinning objects guarantees the security of data passing from managed code to local code. The common ones are arrays and strings. If your code does not need to interact with local code, it does not have to consider its performance overhead. The
pinning the object is to make the object unable to move it during garbage collection (the compression phase). While pinning an object does not cost much, it interferes with GC recycling and increases the likelihood of memory fragmentation. The GC records the location of objects when they are reclaimed, so that the space between them is used when the allocations are rebuilt, but if there are many pinned objects, the memory fragmentation increases. The
Pin can be either displayed or implicit. The display is set with the gchandletype.pinned parameter when using GCHandle, or the fixed keyword is used in unsafe mode. The difference between using the fixed keyword and gchandle is whether the call to Dispose method is displayed. Although it is convenient to use fixed, it can not be used asynchronously, but it is still possible to create a handle object (GCHandle), which is returned and processed at the time of the callback.
implicitly pinned objects are more common, but more difficult to troubleshoot and harder to remove. The most obvious example is passing objects to unmanaged code through platform invoke (P/invoke). This is not just your code —--some of the managed APIs that you call frequently, but it will actually call local code and pin the object. The
CLR will also pin some of its own data, but this usually doesn't require your attention.
Ideally, you should not pin the object as much as possible. If this is not possible, follow the previous important rules and let the pinned objects release as soon as you can. If the object is simply pinned and released, there will not be much chance of affecting the recycle operation. You also have to avoid pinning many objects at the same time. The object being nailed is exchanged for 2 generations or is slightly better distributed in the Loh. According to this rule, you can allocate a large buffer on the large object heap and manage the buffer according to the actual need. or allocate buffers on small object pairs, and then make them upgrade to 2 generations before pinning them. This is better than pinning the object to 0 generations.