Visual Studio 2015 introduced the Diagnostic Tools window including the native memory profiler. The native memory profiler collects Windows ETW events from the heap provider and analyzes them in order to determine the type and call stack of each allocation, as shown in this screenshot. This tool will show all of the allocations made directly on the Windows NT heap, but allocations outside the Windows heap would not show any type information.
Reducing Allocation Overhead Using Custom Heaps
There are often times when minimizing the allocation overhead from the Windows heap is desirable. One options is to use VirtualAlloc to grab a large amount of memory upfront and then manage blocks within that. A common strategy in game development and other high performance applications is to use custom pool allocators, which allocate upfront a fixed-size block of memory upfront for a specific purpose, whether it be for a batch of sprites, or an entire game level. This strategy avoids the constant instantiation overhead of the Windows heap by enabling performance boosting techniques when managing objects, such as keeping like objects in a pool to boost cache performance. In the following example, we are creating a memory pool for allocating Foo objects called mPool:
Acquiring a new object is as easy as simple iterating the address space and casting each block to the appropriate type. This avoids any additional overhead made by using a CRT/Windows allocator like new.
When using heap management optimizations like pools allocators, Windows ETW events cannot be used to track the allocations since allocations from a pool are not relying directly on the OS to create each object allocation.
Adding Support for Custom Heap Events
There are a few steps required to enable the memory profiler to show the type and call stack of each allocation made from a custom heap.
Determining the Type of Each Allocation
The Visual C++ v19.0 compiler (platform toolset v140) introduced support for describing a function as an allocator, as shown below for myMalloc. Both the declaration and definition of the functions must be annotated as follows:
At each call to an allocator function, the address of the call site, the size of the call instruction, and the typeid of the new object are emitted into a new S_HEAPALLOCSITE symbol inside the PDB. When the Windows heap code emits an ETW event with a call stack for an allocation, the memory tool walks the call stack looking for a return address matching an S_HEAPALLOCSITE symbol. The typeid in the symbol determines the runtime type of the allocation. Allocators from both the Windows SDK and CRT have been annotated with __declspec(allocator), so calls into these will provide the type information and call stack to the Visual Studio memory profiler.
When calling into a custom allocator function, the return needs to be casted as follows to ensure the type information will be available in the memory profiler:
Creating a Heap Tracker
In order to enable tracking allocations outside the Windows heap using the Visual Studio memory profiler, we have created a custom heap event API to provide the same allocation information as ETW events provide for the Windows heap. This library ships inside the Visual Studio “15” Preview 2 which can be downloaded You must link to the static library which is found in the C:\Program Files (x86)\Microsoft Visual Studio 15.0\VC\Lib\VSCustomNativeHeapEtwProvider.lib in order to use the API.
After linking to the static library, include the required header so you can create a VSHeapTracker::CHeapTracker object inside the header for your memory pool interface. In order to access this namespace, you must include the header:
Inside your application code, you can then input a name for the heap when you create the heap tracker object. This name will be exposed in the Visual Studio heap snapshot window during a memory profiling session:
Creating Heap Events
The API in the header filer declares multiple types of custom ETW heap events that can be fired for allocate, deallocate, and reallocate. The AllocateEvent declaration is shown below:
To make use of this event, simply call the method from your heap tracker object inside your custom “allocate” function that has been decorated with __declspec(allocator), like we did with myMalloc:
In case you are not yet using the Preview 2 bits and would like to examine the full heap event interface, consult the attached VSCustomNativeHeapEtwProvider.h file at the end of the post.
Viewing Custom Heaps in the Memory Profiler
Since the memory profiler in Visual Studio 2015 worked only with the Windows NT heap, we had to add a new UI control in Visual Studio “15” Preview to enable selecting other custom heaps. When a heap snapshot is taken with a custom heap registered, the heaps will be listed as named by the user in the heap dropdown at the upper right:
The NT (Windows) heap is selected by default and lists the allocations we made on this heap, including the heap tracker and the memory pool itself we created (mPool) represented by the Foo array.
Since we added the heap tracker named FooHeap, this will appear in the heap dropdown and show the subsequent allocations contained in the memory pool when selected. In this case nine Foo objects were allocated in the pool and each has been tracked by the memory profiler. Just as with previous releases, the memory profiler allowing diffing snapshots can be diffed using the “Compare to” dropdown which can help reveal leaks by showing the objects still in memory.
Note that the allocations sourced from a custom heap will not show up in the snapshot summary table since this information is populated by information directly from the Windows heap. The count will only show up in the snapshot itself when the source heap is selected.
Try it out!
You can download Visual Studio with this new capability and try the memory profiler on you own custom heap allocators. Here is a link to the VSCustomNativeHeapEtwProvider header file if you would like to examine the interface but do not yet have Preview 2 installed. Here is a link to the code for the memory pool allocator used in this blog post.