分析 vsperfcmd 的配置文件数据





5.00/5 (4投票s)
构建一个查看器并分析 vsperfcmd 的性能分析报告。
引言
这是另一篇文章 免费分析 Visual Studio 中的 C++ 应用程序性能 的衍生作品,其中作者创建了一个简单的查看器来显示性能分析数据。我的贡献是增加了基本的分析功能,使查看更有趣,并增加了对跟踪性能数据支持。
背景
在第一篇文章中,我们学习了如何为示例性能分析编译应用程序,并使用 Visual Studio 所有版本(包括 Express 版本)自带的工具。如果你有 Visual Studio 的 Premium 或 Ultimate 版本,你将拥有内置的漂亮查看器和分析器,但对于我们其他人来说,虽然我们也有性能分析工具可用,但我们没有查看器,而没有一个像样的查看器进行性能分析意义不大。非常感谢原作者向我们展示了方法并为我们提供了一个查看器。让我们尝试做一些改进。
下面是旧查看器的截图
在我的机器上,查看器有一个小的美学缺陷。性能分析数据在我的区域设置中生成,使用逗号作为小数点分隔符。这会混淆解析器,并显示 wmmain 消耗了 10000% 而不是 100%。 C
更令人恼火的是 wmain
出现了好几次。为了让查看器更有用,我想看到 wmainCRTStartup
作为根节点,并在其下方看到 wmain
。然后我想能够深入查看以获取更多详细信息。下面是使用与上面相同数据文件的新视图。
我给输出添加了一些颜色。最初的想法是使用更多颜色来标记热点和可能的 CPU 占用大户,也标记误报。例如,误报是函数 WaitForMultipleObjects
。这是一个不消耗 CPU 周期的函数。它是一种等待/睡眠,只有在满足特定条件或发生超时时才由操作系统释放。这个函数可以用浅灰色来标记为不重要。
正如你所见,线程在 WaitForMultipleObjects
中花费了 51.97% 的时间,但 CPU 占用大户并不在那里。如果你的应用程序有很多线程,实际上很可能大多数线程都处于某种等待状态。C++/.Net 应用程序可能包含以下线程:后台工作线程、异步 IO 工作线程、垃圾回收器以及等待交互的 GUI 线程。
即使是像 readline
这样的控制台输入调用,最终也会调用 WaitForSingleObject
或 WaitForMultipleObjects
。
它是如何实现的
性能分析数据格式
数据格式相当简单。它是一个分层结构,围绕根节点组织,根节点具有作为子节点的调用者和被调用者。
Root","_wmain"
"Caller","_wmainCRTStartup"
"Callee","ExpensiveMethodB(void)"
"Callee","ExpensiveMethodC(void)"
这种模式对每个调用者和被调用者重复。被调用者 ExpensiveMethodB
也是一个根节点。
Root","ExpensiveMethodB(void)"
"Caller","_wmain"
"Callee","Data::~Data(void)"
"Callee","Data::Data(int)"
如果没有过滤器,当我们显示所有根节点时,会得到重复的节点。我们将尝试避免这些冗余节点。
分析性能分析数据
第一步 - 解析和图形表示
解析过程相当直接,我认为 TreeView 会适合表示调用堆栈。
第二步 - 查找入口点
一种方法是搜索函数 _wmainCRTStartup
并将其设置为根节点。不幸的是,这种方法存在缺陷。该函数会根据编译器版本更改名称。有时它被称为 _wmainCRTStartup
,有时是 _tmainCRTStartup
,我甚至找到过更多。
在扩展中,我们还必须找到所有剩余线程的所有入口点。如果我们使用 windbg 暂停正在运行的应用程序,所有线程的入口点都是 RtlUserThreadStart
,这个函数甚至没有出现在节点中。
我最终得到了一个简单的解决方案。所有没有调用者的节点都应该是入口点,即独立线程的起点(包括 wmain
)。
第三步 - 展开节点
展开根节点时,我需要填写被调用者的信息。我使用了根节点中可用的信息。
"Root","_wmain",14,0,"100,00","0,00"
"Caller","_wmainCRTStartup",14,0,"100,00","0,00"
"Callee","ExpensiveMethodB(void)",4,0,"28,57","0,00"
"Callee","ExpensiveMethodC(void)",10,0,"71,43","0,00"
这看起来确实是正确的。
第四步 - 哦不,数值是错误的
当我进一步深入,进入 ExpensiveMethodB
和 ExpensiveMethodC
时,我很快就注意到数值没有意义。
看,对 std::list::_Tidy(void)
的调用在每个分支中占 42.86%。
让我们看看根节点,看看哪里出了问题
"Root","std::list::_Tidy(void)",6,0,"42,86","0,00"
"Caller","Data::~Data(void)",6,0,"42,86","0,00"
"Callee","_free",6,0,"42,86","0,00"
我明白了。我的第一个方法太天真了。Data::~Data(void)
是唯一的调用者,总共花费了 42.86% 的时间,但在我们的应用程序中,Data::~Data(void)
是从两个不同的函数调用的。当我们深入到这些函数时,我们需要计算每个分支的贡献。
问题在于,仅仅看调用者贡献是不够的,还要看调用者的调用者的贡献,依此类推。实际上,我们必须追溯到根节点。
仅仅考虑根据调用者链计算新值,在树中上下遍历就让我头晕。幸运的是,我找到了一个简单的解决方案。
第五步 - 校正数值
在根节点。数值是正确的。在第二层,父节点(根节点)是正确的。只要父节点数值计算正确,我们就可以重用该数值。在 TreeView 中查找并解析父调用者的数值是一个简单的解决方案,可能不是最优雅的。然后我计算了一个新数值,并根据 TreeView 中的父节点数值进行了调整。
double AdjustedParentCallerValue = double.Parse(TreeViewItem.Item[1]); double weight = AdjustedParentCallerValue / Current caller value; double newAdjustedValue = Callee value * weight;
权重是当前分支对被调用者时间的贡献比例。然后这个值必须乘以真实值。现在计算看起来更好了。
注意事项
调整后的数值是近似值。理论上可能存在差异。如果函数 X 的调用者在执行时不会触发相同的被调用者,那么计算分布比例是错误的。如果被调用者被用作不遵循该分支的调用者的子节点,那也是错误的。不幸的是,我们在这里遇到了一个限制。我们用来生成报告的 Vsperfreport 工具不包含此信息。.vsp 文件(一个巨大的原始文件)可能包含它,但它是二进制格式,我不知道。
插桩分析
第一篇文章展示了如何进行采样分析。这是一种以固定间隔停止进程并记录正在运行进程的当前调用堆栈的技术。这就是为什么 WaitForMultipleObjects 函数被过度表示的原因。采样有一些缺点。
- 如果你的程序运行时间非常短,你可能根本得不到任何样本。
- 如果某些函数在两个采样之间运行,可能会被遗漏。
- 采样在虚拟机中不起作用。
我在 Mac 上开发,在虚拟机中运行 Windows。我根本得不到任何样本。我在这里 找到了原因。Vsperf 分析器使用的 CPU 计数器没有被虚拟化或暴露。但幸运的是,我也发现插桩分析在虚拟机中有效。这是一种通过在每个函数体中插入前导和后导代码来修改二进制文件的方法。这种方法非常精确,而且不像采样那样会遗漏调用。缺点是原始性能分析数据文件会很快变得巨大(几 GB 的数据)。生成的摘要报告大小仍然是可管理的。一个 2 GB 的数据文件生成了一个 450 KB 的报告。查看器在几分之一秒内解析了该报告。
使用插桩二进制文件进行跟踪
必备组件
你需要 Vs2010 或 Vs2012 性能工具。它们通常安装在这里。
"C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Performance Tools"
"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Team Tools\Performance Tools"
否则可以从 Vs2010 性能工具 下载
对跟踪的 CallerCalleeSummary 支持
跟踪的摘要报告与采样分析的报告几乎相同。VsPerfView 应用程序会自动检测正确的格式。
该应用程序是在 vs2010 下开发和测试的。当我在工作中使用 vs2012 进行采样分析时,我的查看器无法读取文件。原因是 Vs2012 添加了一些额外的列,例如平均值。这是一个很好的补充。将来也希望将其添加到查看器中。我还添加了对 vs2012 数据格式的支持。
如何进行跟踪分析
必须在 exe 及其 dll 上运行 VsInstr.exe 工具。它会生成启用跟踪的新二进制文件。这些新二进制文件比原始文件慢得多。所以请耐心等待。
准备二进制文件
SET pt="C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Performance Tools"
%pt%\vsinstr VSPerfNativeSampleApp.exe
开始跟踪
SET pt="C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Performance Tools"
%pt%\VSPerfClrEnv /traceon
%pt%\vsperfcmd /start:trace /output:my_trace_data.vsp
VSPerfNativeSampleApp.exe
%pt%\vsperfcmd /shutdown
%pt%\VSPerfClrEnv /off
%pt%\vsperfreport /summary:all my_trace_data.vsp
/symbolpath:"srv*C:\Symbols*http://msdl.microsoft.com/download/symbols"
使用代码
使用 VS2010 Professional 编译
此项目是用 VS2010 构建的。VS2010 Express 版本可能需要安装 F# CTP。
在 VS2012 下编译
如果你有 VS2012 Professional 版本,除了自动转换外,你还需要手动重新链接对最新版本 FSharp.Core 的引用。不幸的是,在撰写本文时,该程序集在 VS2012 Express 中不可用。这也是我将其开发为 VS2010 的原因之一。
历史
- 2013 年 1 月 2 日 - 首次发布。
- 2013 年 1 月 3 日 - 澄清了关于跟踪和虚拟机的说明。澄清了下载文件的名称。
- 2013 年 1 月 17 日 - 在说明中更正了文件名。将 my_sampled_data.vsp 更改为 my_trace_data.vsp。