[Performance is a key concern for all but the most trivial of apps, and memory usage helps determine how well your app runs. In this series, Pratap Lakshman shows how the Windows Phone Performance Analysis Tool can help you diagnose and fix memory issues and improve app performance. –ed.]
Let’s continue our look at the memory profiling feature of the Windows Phone Performance Analysis tool. Today I’m focusing on the Types view, which presents a conveniently grouped organization of heap activity and can be used to quickly focus diagnosis on specific types and allocating assemblies. Every activity can be tracked back to the assembly causing it, and indeed to the line of code in source (as we shall see in a future post). Your allocations cannot hide from the profiler.
The Heap Summary view, which I covered in my last post, provided a categorized demographic representation of heap activity—springboards to dive from for deeper analysis. Selecting a category and navigating to the Types view reveals details about the participating types, arranged in a tabular form.
The Types view
Here is the table from the memory leak diagnosis case where we navigated to the Types view for the “Retained Silverlight Visuals at End” category.
It can be seen that there are 5 instances of the type ManagedLeak.Page2 allocated from mscorlib.dll, using up 467 KB on average for a total of about 2.2 MB, and accounting for 99% of the memory within the category.
The table lays out—using a combination of text, numerals and graphics—the association between the names of allocated types, a count of the allocated instances and their sizes, and the module (assembly) from where the allocation occurred; this “allocating module” serving as the provenance for sets of instances grouped by their type. The columns in the table support sorting, and sorting by Allocating module can quickly focus diagnosis to appropriate application assemblies.
The profiler records the active execution call stack in response to every managed object allocation. During subsequent post processing of this recorded data it descends frames on the call stack until it discovers the method causing the allocation and displays the module to which the method belonged as the Allocating Module. An “N/A” in the Allocating Module column indicates that the stack frame of the method causing the allocation was not discovered. Recording a deeper call stack improves the chances of discovering the allocating method, and can be configured from the Advanced Settings hive under the Memory Profiler, but beware that it will also bloat the size of the data that needs to be recorded and subsequently transferred back to desktop for post processing.
The Type Name column lists the full name of a type (i.e. including the namespace). For every type name listed there will be corresponding type declaration in code (in user code or Framework code), whose instances have been allocated dynamically; in C# that would be by using the new operator. The attached sample demonstrates how these are reported in the Types view. Running it under the Memory Profiler with the Allocation Depth set to 12 (thrice the default value, for good measure), and navigating to the Types view from the New Allocations category in the Heap Summary view, we see the following table:
Named types
Open the sample and you should see the following snippet that allocates the same named type from two assemblies PhoneClassLibrary1.dll and PhoneClassLibray2.dll using the new operator:
namespace PhoneClassLibrary3 {
public class Bazooka { }
}
// ...
for (int i = 0; i < 101; i++) {
var o = new Bazooka();
}
This is shown in the Types view as two sets of 101 instances of the type PhoneClassLibrary3.Bazooka and while one set has PhoneClassLibrary1.dll as the Allocating Module the other has it as PhoneClassLibrary2.dll.
Arrays
The following snippet allocates one dimensional arrays:
public class Base { };
// ...
for (int i = 0; i < 101; i++) {
o = new Base[0];
}
This is shown as 101 instances of PhoneApp15.Base[] from the same module. The unadorned [] indicates that this is a one dimensional array. For a two-dimensional array, a single comma will be used inside the square brackets (an array of rank 2). This notation extends in the obvious way: each additional comma between the square brackets increasing the rank of the array by one.
Straight forward enough one may think, but there can be a few subtleties:
- There might be allocations happening without explicit use of the new operator in code.
- There might cases where there is no direct mapping from the type name shown to a declaration in code. Especially for complex types where the profiler creates a simplified representation of the type name.
Let us see a few such cases from the same sample and refer to how they are reported in the table above.
Delegate types
The following snippet allocates delegates:
public delegate void Proc();
// ...
public static void method() { }
// ...
Proc o;
// ...
for (int i = 0; i < 101; i++) {
o = method; // synctactic sugar hides the 'new'
}
This is shown as 101 instances of PhoneApp15.Proc from the module PhoneApp15.dll. But notice that there is no explicit use of the operator new in code. Despite that, such allocations cannot escape detection!
Nested types
The following snippet allocates instances of a nested class:
public class Outer {
public class Nested { }
};
// ...
for (int i = 0; i < 101; i++) {
var o = new Outer.Nested();
}
This is shown as 101 instances of PhoneApp15.Outer+Nested from the same module.
Generic types
For constructed generic types the full name of the type includes Culture and Version information of the actual type parameters. Since this information is irrelevant for reporting allocations, the profiler drops them, and instead shows a simplified form of the full name. Consider the following snippet that allocates instances of a constructed generic type:
public class MyCollection
{ }; // ...
for (int i = 0; i < 101; i++) {
var o = new MyCollection
(); }
This is shown as 101 instances of PhoneApp15.MyCollection`1
Nested Generic Types
And the following snippet allocates instances of a nested generic type:
public class OuterGenericDecl
{ public class NestedGenericRedecl { }
}
// ...
for (int i = 0; i < 101; i++) {
var o = new OuterGenericDecl
.NestedGenericRedecl(); }
We have seen that nesting introduces a “+” sign into the type name, and that generic types have their arity encoded into their type name, but the CLS rules for generics also states that “Nested types must have at least as many generic parameters as the enclosing type. Generic parameters in a nested type correspond by position to the generic parameters in its enclosing type.” As expected, this is reported as 101 instances of PhoneApp15.OuterGenericDecl`1
Since the nested type did not introduce any type parameters of its own, it does not have an arity encoded.
Having seen how arrays, nested types and generics are reported, we can figure out what a source declaration would look like by inspecting the type name reported by the profiler; consider this:
PhoneApp15.OuterGeneric`1
+NestedGeneric`1 []
We know that the full name begins with the namespace, have seen that “+” is used to indicate nesting, that the arity of a generic type is encoded as part of its name, and that a nested generic type implicitly re-declares the type parameters of its enclosing generic type. We can infer the source declaration (in C#) to be:
namespace PhoneApp15 {
public class OuterGeneric
{ public class NestedGeneric
{ } }
}
And the allocation to be of the form:
new OuterGeneric
.NestedGeneric [0];
Sure enough that is how it is in the source.
Anonymous types
But what about types that have no names? This snippet allocates instances of anonymous types:
for (int i = 0; i < 101; i++) {
// the below 2 anonymous types will be unified
var o = new { x = 5, y = 3.1415 };
var o2 = new { x = 5, y = 3.1415 };
// this one is a different type than the one above.
var o3 = new { y = 3.1415, x = 5 };
}
For anonymous types in source, the compiler always generates them as generic types with synthesized names. Furthermore, usage of structurally identical anonymous types (i.e. having the same property names and types, and appearing in the same order) within an assembly are unified to be the same type. In the snippet above the two occurrences of new { x = 5, y = 3.1415 } unify to a single type, while new {y = 3.1415, x = 5} remains distinct.
And this is shown as 202 instances of <>f__AnonymousType0`2
In an upcoming post, I shall introduce the Instances view and show how it enables tracing heap activity back to your source code.
More posts in the series:
Part 1: Memory Profiling for Application Performance