The CLRProfiler for the .Net Compact Framework, Part IV: The Call Tree View
This series of posts provides an introduction to using the CLRProfiler for the .Net Compact Framework. In previous posts (part 1, part 2, and part 3) we've looked at various profiler features as we try to solve a performance problem with a sample application. So far, we've learned that our performance problem is due to excessive boxing of value types. We've also learned what type of objects we are boxing and what methods in the sample are causing the boxing to occur.
In some cases, knowing which method is causing the problem is all you need, especially if the method is small and straightforward. However, there are cases where even more detail is needed. In this post I'll use the Call Tree View to determine the exact line of source code that is causing my performance issue!
After you've finished profiling, you can bring up the Call Tree View from the summary page:
The Call Tree View
The Call Tree View shows every allocation made as a result of every method call. For all but the simplest programs the Call Tree View contains a huge amount of data. Fortunately, navigating through all this data isn't very hard once you get used to a few tricks.
Method calls are shown in black and object allocations are shown in green. For example, the following picture shows the initial state of the Call Tree View. You can see that only one method (the program's Main method) has been called at this outermost level. You can also see that several allocations have already been made. These initial allocations are made by the CLR just before it starts executing a program. As you can see, these initial allocations represent several of the common exception types as well as the initial Application Domain.
Along with the individual method calls and object allocations, the Call Tree View also shows summary statistics such as the number of method calls that result from a given call, the number of bytes and objects allocated by a given call and so on. You can sort the data either by number of calls or by the amount of memory allocated.
As you start to expand the tree, you'll notice that one child node at each level is highlighted in bold. The highlighted node represents the method that has allocated the most memory (or generated the most calls, depending on your sort preference). This is the key to drilling into the data quickly - just follow the highlighted nodes until you find the method you are looking for.
Back to the Example
In previous posts we've established that the objects we are boxing are of type Box.Block and that the boxing is occurring in calls to RotateGameBlocks and InitializeGameBlocks. By drilling through the data in the Call Tree View we can find out exactly which line of code in RotateGameBlocks (or InitializeGameBlocks) is causing the boxing to occur.
The following picture shows the result of following the highlighted notes until I have found the RotateGameBlocks method. Some of the methods in the tree look familiar, like Main, and Application.Run, but many methods don't. Method names that you don't recognize are likely part of NetCF's internal implementation. For example, methods that contain "AGL" in their name are part of NetCF's implementation of Windows Forms.
When looking at the child nodes of RotateGameBlocks, we see a green line representing the allocations of Box.Block. The Objects column tells us that a call to RotateGameBlocks causes 14,400 instances of Box.Block to be allocated. Furthermore, we can see that RotateGameBlocks calls ArrayList.set_Item 14,400 times. The fact that the number of calls to ArrayList.set_Item and the number of instances of Box.Block that are allocated are the same indicates that the calling ArrayList.set_Item is the line in my sample that is causing our boxing to occur.
In my next post I'll describe a set of managed APIs you can use to control profiling programmatically.
This posting is provided "AS IS" with no warranties, and confers no rights.