Profiling the Profilers – Part 2: Anonymous Functions, Accidental Captures and Compiler-Generated Code

In the previous post, I looked at tracking down and fixing a memory leak in ANTS Performance Profiler; .NET memory usage gradually increased over time when different regions of the timeline are selected while the “Database Calls” view is open.

When I left off, I’d found one problem – a dictionary mapping UI components to data models wasn’t being cleared when the UI was updated, so the data models were kept in memory when they were no longer needed – but it seemed there was still a leak. Profiling after my initial fix gave the following summary screen in ANTS Memory Profiler:

image1-p2.png

There is still a large increase in the memory used by StackTrace classes between the two snapshots. Selecting the StackTrace class and opening the “Instance Categoriser” view, this time ANTS Memory Profiler shows me two reference chains keeping StackTrace objects in memory:

image2-p2.png

Narrowing it down

I need to work out which of these is the cause of my problem: I could follow both chains and see which one seemed unusual, but ANTS Memory Profiler can help me out here – I know there has been an increase in memory used by StackTrace objects, and I know from my understanding of the code that when a new region of the timeline is selected, data for that new region is read from disc, new StackTrace objects are constructed and the old ones should be cleaned up – so, if StackTrace objects from before I selected a new region of the timeline are still hanging around, those are likely to be my problem. ANTS Memory Profiler allows me to view the reference chains for just these objects using the “Filters” panel:

image3-p2.png

Here, I chose to filter my results to show only objects from my first snapshot that are still alive in my second – that is, the StackTrace objects I think should have been cleared up. After applying the filter, ANTS Memory Profiler now shows me only one reference chain in the Instance Categoriser:

image4-p2.png

This chain of references should tell me what’s holding on to those StackTrace objects from the previous snapshot. Scrolling back, I can see it’s still a SqlTableQueryRow causing the problem – but the class keeping that alive looks interesting:

image5-p2.png

This class – <>c__DisplayClass7 – is a compiler-generated class, and the object referencing it is a Func<IProfileRange, IInternalSourcCodeViewModel>.

Anonymous functions in C# – a quick primer

This pattern of code is typical of that generated by the compiler when compiling an anonymous function – either a lambda expression or an anonymous method – that captures some of its surrounding context. For example:

In this code, the lambda expression assigned to myFunction references the local variable result in that method, so when myFunction is returned to the caller, a reference to result must be maintained so that the Process method can be invoked on it when myFunction is called. The C# compiler achieves this by creating a class behind the scenes: this class has a single method representing the anonymous function, and members for the captured variables. For example, in the above case, the class would look something like this when decompiled with.NET Reflector:

And the actual code for generated the GetProcessResultFunction method will look something like this when decompiled:

The generated code creates an instance of <>c__DisplayClass1, sets the result field to the local result, and then construct an Action<Data>, passing the instance member function b__0 to the constructor.

The Action<> constructor takes a delegate – under the hood, this is a tuple of a function pointer and an optional object reference (and optionally a reference to another delegate, but that is unused here), and in this case, it will be a reference to the <>c__DisplayClass1 instance, and a pointer to its <GetProcessResultFunction>b__0() method.

It’s easy to see how this can cause leaks – an anonymous function is created and this causes an object to be captured by the compiler-generated class, intentionally or unintentionally, which prevents that object from being freed up when it later falls out of scope.

The problem

Returning to the leak in ANTS Performance Profiler and scrolling back a bit further in the instance categoriser, I get to:

image6-p2.png

In ANTS Performance Profiler, there is the option to show the user source code for the method or SQL query they currently have selected. Source code is also cached, but indirectly – that is, rather than caching the code directly, it caches a function that can be called to generate the source for a particular piece of C# source or SQL query.

As source code can sometimes be both very large and expensive to generate (for example, when decompiling code for which source is not available, using the built-in .NET Reflector), we need to be able to adopt different strategies for caching/generating it, and this approach permits that – the SourceCodeViewModelRepository is a collection of these methods for generating that source. The interface for a class which can provide one of these functions is:

That is, it returns a function that given an IProfileRange, returns an IInternalSourceCodeViewModel.

So, what it looks like here is that the function that has been created to generate the source for a particular SQL query has also captured the SqlTableQueryRow object representing that query in the “Database calls” table. The SqlTableQueryRow has a reference to a ProfileRange, and this in turn has references to a number of StackTraces (see the previous article for more on this).

Looking at the source for BuildModel in SqlTableQueryRow:

There’s a few things going on here. First of all, this method actually generates the source code for the event using the GenerateSql method (which takes a SqlEventData object), but stores it in a WeakReference. The method EventSourceHelper.GenerateSqlSourceCodeViewModel checks this WeakReference and if it’s still alive, returns it; otherwise, it looks through the supplied profile range for a SqlEventData with the given sequence handle, and calls GenerateSql on that event.

The reasoning behind this slightly complex mechanism is that it’s likely we’ll want the source soon after requesting the builder function. However we don’t want to keep it hanging around unnecessarily (especially as some source code can be very large), so we can let it get garbage collected – and we can use the builder function we’ve cached to rebuild it if needed.

This BuildModel property wraps all that up in a single anonymous function that takes an IProfileRange and returns an IInternalSourceCodeViewModel – the caller doesn’t need to know about the event handle or SqlEventData at all. Very neat.

Unfortunately, this code is causing a memory leak: the function appears to be capturing some context that keeps a bunch of StackTraces alive, via a ProfileRange held onto by a SqlTableQueryRow. The hint as to what is causing the problem is the fact that the anonymous function is generated by SqlTableQueryRow itself – that is, the function appears to be capturing the object that created it – this.

Looking at the anonymous function, it calls another static method with four parameters:

  1. source, which is a local variable – we would expect this to be captured by the compiler-generated class, but it doesn’t need to include a reference to this – it’s just a WeakReference to a string. This shouldn’t be the problem.
  2. range, which is a parameter to the anonymous function – this doesn’t form part of the capture, so this can’t be the problem.
  3. m_RowData.SequenceHandle, a property on a member variable (of type int).
  4. GenerateSql, the method on SqlTableQueryRow used to generate the SQL string.

So 3 and 4 seem likely candidates – it’s worth taking them each in turn.

GenerateSql method

It’s pretty clear looking at the method signature what’s going on:

The important thing to note is that this is an instance method: it requires an instance of an object to execute – this. When the capture class is generated, a reference to this will be included so that GenerateSql can be called on it.

Fortunately, the code for GenerateSql doesn’t appear to reference the instance at all – I can turn this into a static method without any problems.

A static method doesn’t require an instance to execute, so this no longer needs to be captured in this case, and this part of the leak is now fixed.

The member variable/property reference

In order to access the member variable m_RowData (and therefore its SequenceHandle property) later on, the compiler-generated class needs to capture the object it’s a member of – i.e., this. The compiler can’t know that the value won’t change between the anonymous function being constructed and it being executed, so it has to capture the reference, and get the value of the property when the function is invoked. This is clearly one of the potential sources of my leaks – but the capture seems to be for a legitimate reason, so what can I do?

Well, the compiler can’t know that the property value isn’t going to change, but I know that the row data for a SqlTableQueryRow is readonly, as is the SequenceHandle property on the row data – so I know the value of SequenceHandle (which is an int) cannot change between this anonymous function being created and invoked. That means that rather than passing in a property-read to the function, I can do a by-value copy and pass that in instead:

To make this even more explicit, I can factor out the creation of the function into another method:

The method that is actually creating the anonymous function is now a static method, and its parameters are just a WeakReference and an int; I can now be sure that the anonymous function itself isn’t capturing the outer SqlTableQueryRow class – which will, in turn, mean that the ProfileRange for that SqlTableQueryRow and all its associated StackTraces are no longer held in memory by this function.

Results

The final profiling results after making the above changes confirm that this has been worth it: StackTrace is no longer the biggest user of memory, and the difference in the amount of memory used between the two snapshots is now greatly reduced, as is the overall change in memory use – down from 97MB to 9.5MB:

image7-p2.png

Conclusion

Over these two posts, I walked through the process I went through finding and fixing some memory leaks in ANTS Performance Profiler that would have been very hard to identify purely by hand. ANTS Memory Profiler helped me identify the classes that were not being cleaned up when I expected them to be, and the helped me identify the reason these classes were being held in memory – in one case, a look-up table used for mapping user interactions to underlying data models wasn’t being cleared when the UI was updated, and in another, the compiler-generated backing class for an anonymous function was capturing the object that created it.