События пользовательской собственной кучи трассировки событий Windows

Visual Studio содержит разнообразные средства профилирования и диагностики, включая встроенный профилировщик памяти. Этот профилировщик обрабатывает события ETW от поставщика кучи и анализирует выделение и использование памяти. По умолчанию это средство может анализировать только выделения из стандартной кучи Windows, а выделения вне собственной кучи не отображаются.

Во многих случаях может потребоваться использовать пользовательскую кучу и исключить издержки при распределении из стандартной кучи. Например, можно использовать VirtualAlloc, чтобы выделить большой объем памяти при запуске приложения или игры, а затем управлять собственными блоками в рамках этого списка. В этом случае средство профилирования памяти будет видеть только первоначальное выделение, а не пользовательское управление, выполняемые в блоке памяти. Но с помощью поставщика ETW пользовательской собственной кучи можно указать средству на выделение, осуществляемые вне стандартной кучи.

Например, в проекте, аналогичном следующему, где MemoryPool является пользовательской кучей, будет доступно только одно выделение в куче Windows.

class Foo
{
public:
    int x, y;
};

...

// MemoryPool is a custom managed heap, which allocates 8192 bytes
// on the standard Windows Heap named "Windows NT"
MemoryPool<Foo, 8192> mPool;

// the "allocate" method requests memory from the pool created above
// and is cast to an object of type Foo, shown above
Foo* pFoo1 = (Foo*)mPool.allocate();
Foo* pFoo2 = (Foo*)mPool.allocate();
Foo* pFoo3 = (Foo*)mPool.allocate();

Моментальный снимок из средства Использование памяти без отслеживания пользовательской кучи будет отображать только одно 8192-байтовое распределение и ни одного из пользовательских распределений, сделанных пулом:

Windows heap allocation

Выполнив следующие действия, можно использовать это же средство для отслеживания использования памяти в пользовательской куче.

Использование

Эту библиотеку можно легко использовать в C и C++.

  1. Включите заголовок для поставщика ETW пользовательской кучи:

    #include <VSCustomNativeHeapEtwProvider.h>
    
  2. Добавьте декоратор __declspec(allocator) к любой функции в диспетчере пользовательской кучи, которая вернет указатель на только что выделенную память кучи. Это декоратора позволяет средству правильно определять возвращаемый тип памяти. Рассмотрим пример.

    __declspec(allocator) void *MyMalloc(size_t size);
    

    Примечание.

    Этот декоратор сообщит компилятору, что данная функция представляет собой вызов распределителя. В результате каждого вызова функции будет выводиться адрес места вызова, размер инструкции вызова и идентификатор типа нового объекта для нового символа S_HEAPALLOCSITE. После выделения стека вызовов Windows выдаст событие ETW с этими сведениями. Средство профилирования памяти просматривает стек вызовов, чтобы найти обратный адрес, соответствующий символу S_HEAPALLOCSITE. Сведения об ИД типа в символе используются для отображения типа среды выполнения для выделения.

    Это означает, что вызов, который имеет вид (B*)(A*)MyMalloc(sizeof(B)), будет отображаться в средстве как имеющий типа B, а не void или A.

  3. Для C++ создайте объект VSHeapTracker::CHeapTracker, указав имя для кучи, которое будет отображаться в средстве профилирования.

    auto pHeapTracker = std::make_unique<VSHeapTracker::CHeapTracker>("MyCustomHeap");
    

    Если вы работаете с C, используйте функцию OpenHeapTracker. Эта функция возвращает дескриптор, который будет использоваться при вызове других функций отслеживания.

    VSHeapTrackerHandle hHeapTracker = OpenHeapTracker("MyHeap");
    
  4. При выделении памяти с помощью настраиваемой функции вызовите метод AllocateEvent (C++) или VSHeapTrackerAllocateEvent (C), передав указатель на память и ее размер, чтобы отслеживать выделение.

    pHeapTracker->AllocateEvent(memPtr, size);
    

    or

    VSHeapTrackerAllocateEvent(hHeapTracker, memPtr, size);
    

    Важно!

    Не забудьте пометить пользовательскую функцию распределителя декоратором __declspec(allocator), описанным выше.

  5. При освобождении памяти с помощью настраиваемой функции вызовите метод DeallocateEvent (C++) или VSHeapTracerDeallocateEvent (C), передав указатель на память и ее размер, чтобы отслеживать выделение.

    pHeapTracker->DeallocateEvent(memPtr);
    

    или:

    VSHeapTrackerDeallocateEvent(hHeapTracker, memPtr);
    
  6. При перераспределении памяти с помощью настраиваемой функции вызовите метод ReallocateEvent (C++) или VSHeapReallocateEvent (C), передав указатель на новую память, размер выделения и указатель на старую память.

    pHeapTracker->ReallocateEvent(memPtrNew, size, memPtrOld);
    

    или:

    VSHeapTrackerReallocateEvent(hHeapTracker, memPtrNew, size, memPtrOld);
    
  7. И, наконец, чтобы закрыть и очистить средство отслеживания пользовательской кучи в C++, используйте деструктор CHeapTracker вручную или с помощью стандартных правила области видимости либо используйте функцию CloseHeapTracker в C.

    delete pHeapTracker;
    

    или:

    CloseHeapTracker(hHeapTracker);
    

Отслеживание использования памяти

При наличии этих вызовов для отслеживания использования пользовательской кучи теперь можно применять стандартное средство Использование памяти в Visual Studio. Дополнительные сведения об использовании этого средства см. в документации по средству Использование памяти. Убедитесь, что вы включили профилирование кучи с моментальными снимками, в противном случае использование пользовательской кучи отображаться не будет.

Enable Heap Profiling

Чтобы просмотреть отслеживание пользовательской кучи, воспользуйтесь раскрывающимся списком Куча, расположенным в правом верхнем углу окна Моментальный снимок, для смены представления с Куча NT на вашу собственную кучу.

Heap Selection

Используя приведенный выше пример кода с MemoryPool для создания объекта VSHeapTracker::CHeapTracker и собственным методом allocate, вызывающим метод AllocateEvent, теперь можно увидеть результат пользовательского выделения — три экземпляра общим размером 24 байта с типом Foo.

Куча NT по умолчанию выглядит так же, как и ранее, но к ней добавлен объект CHeapTracker.

NT Heap with Tracker

Как и для стандартной кучи Windows, это средство можно использовать для сравнения моментальных снимков и поиска утечек и повреждений в пользовательской куче, как описывается в основной документации по средству Использование памяти.

Совет

Visual Studio также содержит средство Использование памяти в наборе инструментов Профилирование производительности, который доступен при выборе пунктов Отладка>Профилировщик производительности или при нажатии сочетания клавиш Alt+F2. Эта функция не поддерживает отслеживание кучи и не отображает пользовательскую кучу, как описано здесь. Эта возможность доступна только в диалоговом окне Средства диагностики, которое можно открыть, последовательно выбрав Отладка>Окна>Показать средства диагностики либо нажав сочетание клавиш Ctrl+Alt+F2.