在Visual Studio 15 Preview 2 中自定义本地ETW 堆事件

[原文发表地址] Custom Native ETW Heap Events in Visual Studio 15 Preview 2

[原文发表时间] 2016/5/25

背景

在Visual Studio 2015 中介绍了包含本地内存分析诊断工具。本地内存分析工具从堆处理程序那儿收集Windows ETW事件并分析它们来确定每一个分配的类型和调用栈,如截屏所示。这个工具将显示所有在Windows NT堆直接产生的分配,但是Windows堆以外的分配将不显示任何类型信息。

使用自定义堆减少分配开销

从Windows堆减少分配开销通常是可取的。其中一种方法是使用VisualAlloc来提前获取大量内存然后用它来管理其中的块。在游戏开发和其他高性能应用程序中通用的策略是使用自定义池分配器,他们提前以特殊目的分配一个固定大小的内存块,无论是为了一个小脚本,或者整个游戏层面。当管理对象的时候,这一策略通过启用性能优化技术避免了Windows堆的常数实例化开销,例如在池中保留对象来提高缓存性能。在如下的例子中,我们为分配Foo对象创建一个叫mpool的内存池:

获取新对象和简单的地址空间迭代以及给每一块分配合适的类型一样容易。这个避免了使用CRT/Windows分配器带来的额外的开销,例如new

当使用堆管理优化技术如池分配器时,Windows ETW事件不能用来跟踪分配,因为从池来的分配不会在OS上直接回复创建每个对象分配。

添加自定义堆事件支持

启用内存分析工具来显示自定义堆产生的每个分配的类型和调用栈需要一些步骤。

确定每个分配的类型

Visual C++ v19.0编译器(平台工具集 v140)对把一个函数描述成一个分配器这样的支持进行了介绍,如下显示的myMalloc。函数的声明和定义应该被标注如下:

对于每一个调用的分配器函数,调用点的地址,调用指令大小,以及新对象的typeid被发送到PDB文件中一个新的S_HEAPALLOCSITE 符号中。当Windows堆代码发出一个带有调用堆栈分配的ETW 事件,内存工具在堆栈中寻找一个和S_HEAPALLOCSITE 符号文件相匹配的返回地址。符号文件中的typeid决定着分配的运行时间类型。来自Windows SDK和CRT的分配器都用 _declspec(allocator) 进行标注,所以对这些分配器的调用将给Visual Studio内存分析提供类型信息和调用堆栈。

当调用一个自定义的分配器函数时,返回值需要被塑造成如下形式,确保类型信息在内存分析中可用。

创建一个堆跟踪器

为了使用Visual Studio 内存分析工具,对Windows堆外部启用分配跟踪,我们创建了一个自定义的堆事件API来提供和为Windows堆提供的ETW事件相同的分配信息。这个库在Visual Studio “15”Preview 2 内发布,你可以下载。你必须链接到静态库中才能使用这个API,这个静态库可以在C:\Program Files(x86)\Microsoft Visual Studio 15.0\VC\Lib\VSCustomNativeHeapEtwProvider.lib中找到。

链接到静态库之后,包含需要的头文件,因此你可以为你的内存池接口在头文件中创建一个VSHeapTracker::CHeapTracker对象。为了能访问这个命名空间,你必须包含这个头文件:

在你的应用程序里面,当你创建堆跟踪器对象时,你可以给你的堆起个名字。这个名字将在内存分析会话期间显示在Visual Studio 堆快照的窗口中:

创建堆事件

在头文件管理器中的API,声明了自定义ETW堆事件的多种类型,可以用来分配,释放和重新分配。分配事件声明如下所示:

为了使用这个事件,简单地从你的自定义“分配”函数中的堆跟踪器对象调用方法,你的自定义分配函数被_declspec(allocator)声明, 就像我们对myMalloc做的一样。

如果你没有使用Preview 2但想用它来检查全部的堆事件接口,请查阅博客最后附上的VSCustomNativeHeapEtwProvider.h 文件。

在内存分析工具中查看自定义堆

由于内存分析工具在Visual Studio 2015中只能对Windows NT堆起作用,我们不得不在Visual Studio “15“中增加新的UI控制来开启选择其他的自定义堆。当捕获到一个含有自定义堆注册的堆快照时,这些堆就会在右上方的heap下拉菜单中以用户起的名字显示出来。

默认选择的是NT(Windows)堆并且列出了我们在这个堆上的分配,包含堆跟踪器以及内存池自身,以Foo[]数组代表我们创建的 (mPool)。

由于我们添加了叫做FoolHeap的堆跟踪器,它将出现在堆下拉框中,并且在选中时显示内存池所包含的子分配。在这个例子中,九个Foo对象被分配在池中,每一个对象通过内存分析工具来跟踪。如之前的发布版本,内存分析工具允许使用“Compare to“下拉框把不同的快照进行对比,这个能帮助我们通过显示对象展现出内存泄漏的问题。

注意这些分配来源于自定义堆,它们将不会在快照汇总表里面显示,因为这些信息直接来源于Windows堆。这些数量只会当源堆被选择后,显示在快照本身里。

试着做一做!

你可以从下载具有这个新功能的Visual Studio并且试着用你自己定义的堆分配器进行内存分析。如果你想检查接口而没装Preview2的话,这个是VSCustomNativeHeapEtwProvider头文件的链接。这个是这篇博客中使用的内存池分配器的代码