Analyze memory usage in Timing Captures

Use Timing Captures to find memory growth, page faults, unfreed allocations, and allocations that were never accessed. Start with high-level counters to find an interesting time range, then use Range Details or the Memory layout to identify the allocating code path.

When you enable memory allocation tracking, PIX records the allocations and frees made while the capture is running. PIX uses this data to compute memory counters that you can graph in the Metrics layout, and to build the Allocation Stack Tree and allocation lists in Range Details. A typical workflow is:

  1. Use the memory counters in the Metrics layout to find a period of time where something unexpected happens, such as steady growth or unfreed allocations.
  2. Navigate to that period in the Timeline.
  3. Use Range Details to find the call stacks responsible for the memory.

Capture memory data

Enable the memory data that matches your investigation before you start the Timing Capture:

Capture option Use it to
VirtualAlloc/VirtualFree events Track allocations made with VirtualAlloc and frees made with VirtualFree.
HeapAlloc/HeapFree events Track allocations made with HeapAlloc and frees made with HeapFree.
Custom allocator events Track title-defined allocators that are instrumented with PIX memory events. For details, see Custom allocator memory profiling.
Page Fault events Show page faults in the Timeline and Element Details views.
Memory Access Sampling Sample memory reads and writes so the Memory layout can show which allocations were accessed.

Select CPU Samples when you enable Memory Access Sampling. PIX records the register data used to identify sampled memory accesses with CPU samples, so memory access sampling is available only when CPU sampling is enabled.

Configure symbols before you analyze memory call stacks or access samples. Add your game's PDB and executable paths to symbol path settings so PIX can resolve allocation stacks and sampled instruction addresses.

PIX records memory allocations that occur while the capture is running. If you need startup allocations, take the capture by using Capture at launch.

Use the Metrics layout, System Monitor, or the Memory Usage Timeline lane to find the time range that needs investigation.

The System Monitor CPU memory counters include:

  • Working Set Size: the title process's current working set size.
  • Page File Usage (Commit): the private memory committed for the running process.
  • Page Fault Count: the process's number of page faults.

When allocation events are captured, PIX also computes three sets of allocator metrics. Each set is separated by allocator, for example VirtualAlloc and HeapAlloc. In the Metrics selector, find them in the Memory Allocators group for each process, with one child group per allocator:

  • Event Rate, such as Event Rate - VirtualAlloc and Event Rate - HeapAlloc: the rate of allocations over the duration of the capture. Spikes show bursts of allocations.
  • Memory Usage, such as Memory Usage - VirtualAlloc and Memory Usage - HeapAlloc: the amount of memory allocated over the duration of the capture. A steady stair-step pattern indicates continuing growth.
  • Unfreed Allocations, such as Unfreed Allocations - VirtualAlloc and Unfreed Allocations - HeapAlloc: memory from allocations that aren't freed before the end of the capture, plotted at the time each allocation was made. Nonzero values show points in time where allocations were made but not freed.

Use these metrics to find growth, bursts of allocations, or periods with unfreed memory. Then select that period in the Metrics graph, right-click, and select Selected Range to Timeline to zoom the Timeline to the same range. You can also sync Range Details to the selected Metrics range. For example, if you select a period that contains unfreed allocations, Range Details shows a nonzero Unfreed Size for the allocator in the Allocation Stack Tree. Expand the tree to find the call stacks responsible for the unfreed memory.

The Memory Usage lane in the Timeline layout graphs memory over time in the Timeline. The lane can include API object memory, allocator memory usage, and the title process working set or title CPU memory usage when the capture contains that data.

Investigate unfreed allocations in Range Details

Use Range Details when you know the time range where memory grows and need the call stack that allocated the memory. PIX records the call stack for each memory allocation and free, then aggregates those stacks into an allocation tree. The tree includes all allocations that occurred within the selected time range.

  1. Select the time range in the Timeline or Metrics graph.
  2. Open Range Details.
  3. Set Item Type to Allocation Stack Tree.
  4. Sort by Unfreed Size.
  5. Expand the allocator and call stack nodes with nonzero Unfreed Size.
  6. Select !Freed to show only stack nodes whose inclusive unfreed size is nonzero.

Understand the Allocation Stack Tree

The Allocation Stack Tree groups stacks first by allocator, so allocations and frees made with HeapAlloc stay separate from those made with VirtualAlloc. Each allocator is a top-level node in the tree:

  • VirtualAlloc contains stacks for VirtualAlloc allocations.
  • HeapAlloc by Handle is further grouped by heap handle, so you can analyze memory usage on a per-heap basis.
  • Title Allocators by Id contains custom allocators, grouped by allocator ID.

Use the Selector panel on the left side of Range Details to choose which allocators appear in the tree.

Expand an allocator node to see the aggregated stacks for all allocations attributed to that allocator in the selected time range. Use these columns to identify the source of growth:

  • Unfreed Size: memory allocated in the selected range that isn't freed by the end of the capture.
  • Allocated Size: total allocated size attributed to the stack node.
  • Allocation Count: number of allocations attributed to the stack node.
  • Memory Type: the allocator represented by the node.
  • Module: the module for the stack frame.

The Unfreed Size, Allocated Size, and Allocation Count values are always relative to the node they appear on, so they include every allocation made through that node's call path. Use this to find which call path is responsible when a function is reached from more than one caller. For example, if a function that allocates memory is called from both Process and UpdateEnemyPositions, it appears under two distinct call stacks. Compare the Unfreed Size of the Process and UpdateEnemyPositions nodes: if only the Process node has a nonzero value, all of the unfreed memory came from the call stack that goes through Process.

Filter and customize the tree

Use the Range Details filter bar to show only stacks that contain a specific string. Select !Freed in the filter bar to show only call stacks with a nonzero Unfreed Size.

Open Display Options for the allocation stack tree to change how stacks are displayed. Callee on top inverts the tree so the callee is shown at the top of the stack instead of at the bottom. Clamp Stack Depth limits stacks to 20 frames to improve performance. Show Addresses and Truncate Module Names change the frame text. You can also change which columns the tree displays.

Inspect individual allocations

Use flat allocation data when you need a specific allocation event instead of an aggregated call tree.

  1. Select the time range you want to inspect.
  2. Open Range Details.
  3. Set Item Type to Virtual Memory Allocations or Heap Memory Allocations.
  4. Sort or filter the item list by columns such as Size, Unfreed Size, Flags, Heap, Time, or Thread, depending on the item type. The list is sorted by time by default.
  5. Select an allocation to see its call stack and related information in Element Details. For Virtual Memory Allocations, Element Details also links to the corresponding VirtualFree calls, if any.

When memory access sampling data is available, allocation item lists can include Accessed so you can tell whether the allocation was sampled during the selected region.

Analyze allocations and memory accesses in the Memory layout

Use the Memory layout when you need a visual map of allocations, lifetime filters, access sample counts, and memory read/write samples.

  1. Open the Memory layout.
  2. Select a time range in the Memory Usage graph.
  3. Choose the Memory Type to analyze.
  4. Select Analyze.
  5. Use Lifetime, Allocation Size, and Access Sample Count to filter the memory map.
  6. Select an allocation to view its details.
  7. Use Memory Animation to process sampled accesses and add Timeline links for individual reads and writes.

The Lifetime filter includes Memory Leaks, which shows allocations made in the selected range and not freed by the end of the analysis window. The Access Sample Count filter includes 0, which helps find allocations that PIX did not sample reads or writes from during the selected range.