Usecase for tracking down high % GC time (related to gen 2)

jaap.q42jaap.q42 Posts: 36 Bronze 2
Hi All,

I would like to track down an issue with a high % GC with a relation to gen 2 collections. The gen 2 collections follow eachother quite quickly for a period of time.

Would I be able to track this issue with the ANTS memory profiler? Any ideas on how to do this.

Thanks,

Jaap

Comments

  • The memory profiler is really only designed for finding issues related to the amount of memory being used rather than performance issues with the garbage collector. It can help out a bit with these kinds of problem, though.

    Note that the % time in GC performance counter is quite hard to read, and a high value might not be telling you anything particularly useful. It's only updated after a garbage collection has taken place, and it represents the % of time spent in the most recent garbage collection relative to the amount of time since the preceeding collection.

    This means if you get a situation where two GCs happen in rapid succession and then nothing happens for a while, it will appear to read consistently high when in fact the garbage collector is having no impact on performance whatsoever. Because of the way this counter works, it's generally only indicating an issue if it's both giving a high value and changing frequently (meaning that there are a lot of expensive garbage collections occurring).

    If you're using server GC (which is the default for ASP.NET or things like Windows Services), a high value for this counter is quite likely as this garbage collector favours infrequent expensive collections and doesn't run concurrently. (This strategy reduces the overall time spent garbage collecting, but means that individual garbage collections may be more noticeable)

    Additionally, the memory profiler will push this value up, especially after you've taken a snapshot, as it performs housekeeping during .NET garbage collections (the more .NET rearranges memory during a garbage collection, the bigger this effect will be)

    If there is a genuine problem, there are really two things you can look at to improve performance.

    The first is to increase the speed at which a GC is completed. The time to perform a garbage collection is related to the number of objects that survive it, so the trick is to make sure as many objects get removed in one collection as possible. How you do this depends on the architecture of your application: generally if you have a long-lived object (like your main window, or a cache object), it's a good idea to make sure it nulls out references to more short-lived objects as soon as they're not going to be used any more. The memory profiler can find such dangling references for you.

    The second is to reduce the number of generation 2 garbage collections. If you're calling GC.Collect manually, you will probably be forcing generation 1 objects into generation 2 prematurely, so removing those may help (if you do have to call GC.Collect, do it only after dereferencing as much as possible, preferably in a callback when the application is idle). If you're not using GC.Collect, it gets a little more tricky:

    Objects on the Large Object Heap are often only collected during a full collection, so reducing the number of these that you are allocating will help (re-use old ones if possible, or allocate several small objects that don't end up on the LOH). This will also help with overall memory usage as the heap will get less fragmented. The memory profiler can identify which objects are on the LOH and what is referencing them.

    The hardest problem to fix is excessive promotion from generation 1. You can use the Generation 1 promoted bytes/sec performance counter to look for this problem: if it's high and GC time is high then what is probably happening is that the generation 1 garbage collections are occurring at the wrong time and a lot of stuff is getting put into generation 2 and being quickly dereferenced, which is pretty much the worse case scenario for the garbage collector. The memory profiler can identify what is in generation 1, so if you can take a snapshot at the appropriate moment you might get an idea of what is causing the problem, but there is an element of pot luck involved here. .NET should automatically increase the size of generation 1 when it detects this happening to make collections less frequent, so the problem is going to be some subtle interaction between your program and the garbage collector.
    Andrew Hunter
    Software Developer
    Red Gate Software Ltd.
Sign In or Register to comment.