中文:http://www.microsoft.com/china/MSDN/library/enterprisedevelopment/softwaredev/enterpriseperformance.mspx?mfr=true
John Robbins
本文基于 Visual Studio 2005 的预发布版本。文中包含的所有信息均有可能变更。
本文讨论:
• | 分析器的内部工作方式 |
• | EPT 的灵活功能 |
• | 一个供分析的示例应用程序 |
代码可从以下位置下载:
EnterprisePerformance.exe (258KB)
快速代码仍然很受欢迎。即使我用来键入本文的计算机具有足够的能力和内存,能够同时控制一座原子能发电厂、一个火星漫游计划以及美国西部上空的空中交通,并且仍然具有充足的能力来处理星际探索中的 SETI 数据包,但这并不意味着开发人员不再需要担心其代码的速度和效率。在过去进行 Win32® 本机开发的日子里,我们不仅需要担心速度,而且还要担心 PC 平台上那些令人讨厌的访问冲突(对于你们这些老家伙,还有“全局保护错误”和“不可恢复的应用程序错误”)。尽管托管代码已经消除了其中的一些担心,但它只意味着您所经历的那些性能问题可能比以前更加难以捉摸。主要原因是,在使用托管代码时,我们不具有在进行本机开发时所拥有的简便的运行库视图。
有许多次,当我正在使用客户端时,我不知道如何解决恶性的性能问题。当然,这些性能问题不会出现在任何测试系统中;它们只会出现在真实世界的生产中。由于公共语言运行库 (CLR) 是黑盒,因此如果我希望找到在测试系统中重复性能问题的方法,则很难预测会发生什么事情。尽管在市场中有一些第三方商业性能工具,但这些工具中的大多数都会对系统造成过多的干扰,以至于根本不能考虑在生产系统中使用。这也就是当我看到 Microsoft 将提供一个全新的分析器 — Enterprise Performance Tool (EPT) 以作为 Visual Studio® 2005 Team Developer Edition 的一部分时,感到如此兴奋的原因。它是我可以真正考虑在生产系统中使用的第一个分析系统,因为它提供了一些非常轻便的收集性能数据的手段。因为我曾经领导过一种最畅销的商业分析器的开发工作,所以我能够理解在不产生太多系统开销的情况下收集有用分析数据的困难程度。
在本文中,我将介绍 EPT 的基本原理,并向您说明如何开始使用它。因为分析器所具有的复杂性,所以在将来某一期中,我将讨论如何使用 EPT 来跟踪您可能在同事的代码中遇到的实际性能问题(我知道您的代码非常完美!)。请记住,EPT 正处在测试阶段(我使用的是 Burton Beta 1 刷新位版本 40607.83),并且在该产品发布之前,可能会对 UI 或某些特定步骤进行更改。在对 EPT 进行介绍之前,我希望花点儿时间谈论一下分析器通常是如何工作的,以便您可以更好地了解是什么使 Enterprise Performance Tool 变得如此与众不同。
分析器的基本原理
在您编写分析器时,可以选择两种收集数据的方式中的一种:探测和采样。这两种方式都十分有效,但是每种方式都有它的折衷方案。探测分析器收集数据的方式是在应用程序中插入探测或挂钩,以便在程序执行该挂钩时就调用分析器运行库。要放置探测,分析器需要在编译步骤中将应用程序仪表化,重写已经编译的二进制文件,或者即时将应用程序仪表化。要查看基于 .NET 的应用程序的探测分析器方法示例,请阅读 Aleksandr Mikunov 的一篇非常出色的文章 —“Rewrite MSIL Code On the Fly with the .NET Framework Profiling API”(该文章摘自 MSDN®Magazine 2003 年 9 月刊)。当我开始讨论 EPT 的时候,您将看到它使用术语“仪表化”来表示探测方法。
探测分析器方法的主要优势在于,当应用程序执行时,将始终调用所插入的探测。这样,分析器运行库将对运行具有完整的认识,因此在生成关键信息(例如,函数之间的父子关系)时可以确保正确,并且分析器可以报告完美的调用树,以便您可以轻松找到花费最长时间的调用路径。使用探测分析器时,没有什么事情能够阻止开发人员只在函数入口和出口处插入探测。可以在源代码行级别放置额外的探测,以便您对函数具有完整的认识。
但是,探测分析器所提供的详细视图具有一些缺点。第一个缺点是仪表化方案使用起来可能很麻烦,并且因为它是在二进制级别重写,因此存在很多可能引入潜在错误的领域。正如您可以想像到的那样,这些探测还占用了空间,从而导致一些代码膨胀和较低的性能。对于完全仪表化的应用程序,探测分析器可能会导致速度大幅度下降,以至于几乎不可能在生产系统上运行仪表化的二进制文件,从而使您在最需要该分析器的时候却无法利用它。
正如其名称所暗示的那样,采样分析器按照预先定义的时间间隔获得应用程序中正在执行的操作的快照。大多数开发人员都没有意识到 Microsoft 总是在他们的开发环境中随附了一个采样分析器。它被称为调试器! 如果您开始调试应用程序,并且每隔 30 秒左右就中断至调试器,则您可以注意到应用程序线程正在何处执行,以便很好地了解应用程序在一次运行过程中执行了哪些操作。我已经通过手动完成采样分析器的工作,解决了很多生产性能问题。
使采样分析器如此有价值的原因在于,它们具有比探测分析器小得多的系统开销。这意味着,您可以有更高的机会在生产系统中使用它们,而不会使服务器疲于奔命以至于停机。采样分析器的问题在于,从应用程序中获得的所有样本很有可能根本不显示任何代码。例如,如果您具有大量使用数据库的应用程序,则所有样本都可以来自数据库访问程序集的内部。最后,只抓取每个线程的当前执行指令的传统采样分析器会使得确定方法之间的父子关系变得十分困难,因而确定性能最差的执行代码途径要困难得多。
Enterprise Performance Tool 的基本原理
在了解分析器的操作方式之后,我就可以讨论 EPT 所采取的方式了。简单地说:它既是采样分析器,又是探测分析器(Microsoft 称之为“仪表化”)。其思想是,您在开始时将通过采样分析器来查看应用程序性能,以获得常规性能特征,以便您可以开始将注意力集中于应用程序的热点问题上。在您了解具有一些问题的程序集之后,就可以求助于仪表化分析以查看特定的问题领域,以便修复它们。当然,如果您要执行单元性能测试,则没有什么能够阻止您直接转到对特定模块进行仪表化,以便在聚焦方案中查看它们的性能。
使 EPT 采样分析器有趣的部分原因在于,您可以使用大量项目来触发样本。默认的采样点是时钟周期,并且可能是您总是使用的采样点。默认情况下,每一百万个时钟周期采样一次,但是您可以将采样间隔的时钟周期数更改为您喜欢的任何值,可是该值越小,EPT 所导致的系统开销就越大。对于生产服务器,您可以将该数字设置为某个非常高的数字(如五百万),以使系统开销保持在合理的水平,同时不会完全破坏进程中的可用性。正如您预料的那样,每五百万个时钟周期采样一次将意味着您需要使应用程序运行相当长的时间,以便在您的热点区域中获得良好的样本分布。
如果您的应用程序使用了很多内存,则可以选择让 EPT 采样分析器改为在出现页错误时触发。这样,您就可以在数据被交换出 RAM 时获得性能快照,并且可以看到是谁在执行推送操作。如果初始分析器运行表明您在代码外部的区域中花费了大量时间,则可以告诉分析器改为基于系统调用来完成采样。如果您要分析具有大量线程的多线程应用程序,则该采样统计信息会对您在从用户模式转换到内核模式(这表明某些线程可能会不必要地在内核对象上阻塞)时的数据进行拍照。您可以用于采样触发器的最后一些值是 CPU 所支持的各种性能计数器,例如,分支计数或缓存丢失。这是一个只有极少数人才确实需要的高级选项,但是如果您确实需要该数据,那么知道该数据存在也不错。
那些忙碌的 Redmontonian 还解决了调用堆栈问题 — 这是对有用的采样分析器造成障碍的最大问题之一。正如我在前面提到的那样,大多数采样分析器在采样时只是对当前正在执行的指令进行拍照。Microsoft 解决了如何将极快的堆栈遍历结合到他们的采样分析器部分中,以便您能够获得样本的好处,并且知道在执行该样本时是如何到达那里的。这使得将这些快照与代码重新关联变得更加容易。
在讨论您可以分析的应用程序之前,我想提几件您很可能觉得有趣的事情。第一件事情是,如果您认为 Microsoft 是从头开发该性能工具的,那么您只猜对了一半。在 Microsoft 内部,开发团队一直在使用 EPT 的前身(名为 Call Attribute Profiler (CAP),它使用仪表化)和 Low Overhead Profiler (LOP) — 一个采样分析器。由于 Microsoft 开发了这些工具以收集有关应用程序(例如,操作系统和整个 Office 套件)的性能信息,因此它们甚至不会给您的应用程序带来什么负担。我曾经使用过 EPT 的前身,所以我可以告诉您公共版本使用起来会容易多少。此外,它们还具有一些极为有趣的功能(稍后我将予以讨论)。
第二个有趣的事项与 EPT 所支持的技术有关。尽管某些人可能认为由于 Microsoft 非常偏重于 .NET Framework,因此无法将 EPT 用于 Win32 应用程序或本机代码,但 EPT 团队实际上已经承诺支持所有的 Win32 本机应用程序以及 .NET 代码。这意味着,无论您使用哪种技术(ASP.NET、Windows® 窗体、MFC 或 Win32 服务),您都具有完全的采样和仪表化支持。您将看到,在 Visual Studio .NET 中,跨技术使用 EPT 没有任何差异。
实际的 EPT 设置非常平常;只需从 Visual Studio .NET 安装程序的“Enterprise Tools”树控件中选择“Enterprise Performance Tool”即可。当然,因为您知道 EPT 仍然是一个测试产品,所以您的第一个反应可能是运行虚拟 PC,并在那里安全地包含所有内容。但是,为了执行采样分析,EPT 使用内核模式设备驱动程序来响应 CPU 性能计数器中断,不过令人遗憾的是,虚拟 PC 没有实现计数器。它也没有模拟高级可编程中断控制器 (APIC),而这两者都是内核设备驱动程序完成其工作所必需的。好消息是,如果您没有额外的计算机以便安装 EPT,那么您也并非完全不幸,因为仪表分析器仍然能够工作。如果您没有多余的计算机以便安装 EPT,那么这是一个让公司为您购买另一台计算机的好借口。
Animated Algorithm
要学习任何工具的用法,您都需要一个合适的示例应用程序,以便能够最佳地利用该工具。在测试周期的这一时刻,EPT 没有随附任何示例,但是在我的硬盘上已经有了一个完美的分析器示例。早些时候,我正在尝试解决如何在 Windows 窗体应用程序中使用多线程的问题,因此我编写了一个名为 Animated Algorithm 的了不起的小程序,该程序可实时激活大量排序算法。图 1 显示我的示例应用程序已经准备好排序。
图 1 正在工作的 Animated Algorithm
Animated Algorithm 使您可以在窗体的组合框中,从 15 个不同的排序算法中进行选择。“Options”菜单使您可以选择各个元素交换或设置之间的休眠时间,以便您可以降低图形更新的速度。
我不久前使用 Microsoft® .NET Framework 版本 1.1 编写了 Animated Algorithm,因此您不会在代码中找到任何奇特的泛型或新的 BackgroundWorker 项。NSORT 程序集中的排序算法来自由 Jonathan de Halleux、Marc Clifton 和 Robert Rohde 张贴到 The Code Project 上的一篇优秀文章(请参阅 Sorting Algorithms In C#),该程序集将算法封装到公共结构中,以便您可以轻松地替换执行元素交换和设置的类。因为它们具有非常好的体系结构,所以我需要关心的所有内容为 UI 部分。
在本文的其余部分中,我将分析 Animated Algorithm 程序。如果 EPT 团队将该程序作为示例应用程序随附在产品中,则会非常棒。(哈哈。)
EPT 入门
在 Visual Studio 2005 Beta 1 中,在哪里可以找到 EPT 当然是不明显的。EPT 在您启动 Performance Wizard(它位于“Tools”菜单下)时启动,并且无论是否打开项目,它都存在。请记住,Performance Wizard 所创建的性能会话不是项目的一部分;它们实际上是具有自己的 IDE 窗口(称为 Performance Explorer)的单独文件。您可以通过从“File”|“Open”对话框中选择 PSESS 文件,来打开您创建的性能会话。
如果您在单步执行 Performance Wizard 时没有打开项目,则所产生的性能会话将与您指定的二进制文件相关联。但是,在测试版中,在您指定要运行的二进制文件时,必须打开关联的项目。我只是想顺便提一下这个小小的技巧,因为当我第一次遇到该问题时,它确实让我困惑不已。
在您启动 Performance Wizard 以后,呈现在您面前的第一个屏幕要求您选择要分析的应用程序。如果您打开了一个可生成多个程序集的项目(如 Animated Algorithm),则只能从该向导中选取一个程序集。如果要进行采样,则只选取这一个程序集是很好的,因为 EPT 采样会分析加载的所有程序集(包括那些来自框架类库的程序集)。但是,如果您要对多个程序集执行仪表化分析,则 Performance Wizard 只选择这一个程序集,因此您将需要在 Performance Explorer 中所生成的性能会话中指定其他项目或程序集。稍后我将向您说明如何完成该工作。
在选择了要在性能会话中使用的程序集或项目之后,您必须选取分析方法。在 Performance Explorer 中的任何位置,您都可以在采样和仪表化之间切换,以满足自己的需要;您在该向导页中进行的选择只表示您最初希望执行的操作。在选择了分析方法之后,向导就基本完成了。对于 EPT 的最终版本,您将在 Performance Wizard 中具有用于指定附加信息的更多选项。最终版本还将使您可以直接从 Performance Explorer 中创建性能会话。
图 2 显示了 Performance Explorer 在刚刚完成 Performance Wizard 步骤以创建 AnimatedAlgorithms 项目的仪表化运行之后的窗口。要添加另一个项目的输出二进制文件,请右键单击“Targets”文件夹,然后从上下文菜单中选择“Add Target Project”。如果要添加与该项目没有关联的特定二进制文件,请选择另一个选项 —“Add Target Binary”。如果您已经选择了“Add Target Project”,则可以在产生的对话框中从已打开的解决方案中选择其他项目。
图 2 Performance Explorer
如果您已经选择了仪表化运行(它由绿色启动箭头下面的下拉列表框中的文本表示),则二进制文件仪表化将在程序执行之前发生。如果您不希望针对运行仪表化某个特定的二进制文件,则请右键单击该二进制文件,并取消选中“Instrument Binary”菜单选项。
如果您已经选择了采样分析,并且希望附加到某个正在运行的项目,则单击“Attach/Detach”按钮(“Start”按钮右侧的斜向箭头)将呈现“Attach Profiler to Process”对话框。通过 EPT,您可以根据需要附加到任意多的进程,以便获得对应用程序的认识。“Attach Profiler to Process”对话框还允许您从特定的二进制文件中分离分析。在将来的某一期 MSDN Magazine 中,我将更详细地讨论如何附加到现有的进程(特别是为了进行 ASP.NET 性能调整)。
Performance Explorer 窗口顶部的最后一个按钮是无所不在的“Properties”按钮。在启动分析运行之前,您可能希望浏览一下性能会话属性,以设置几个关键属性。第一个属性位于“General”选项卡上,它是您希望为性能会话存储性能报告的位置。在分析项目时,默认设置是将报告存储在与解决方案相同的目录中。但更好的做法是将性能会话和它们的相应报告放置在它们自己的目录中,以便您可以更容易地存储特定的运行集。这样还可以更容易地分析之前和之后的情况,以便查看您所进行的代码更改的影响。
在“General”选项卡上,您还可以在仪表化和采样分析之间切换(这会更改在 Performance Explorer 中显示的值)。在我进行的性能调整中,我喜欢将特定的会话专用于单个类型的分析,以避免出现与报告有关的混淆。没有任何事情阻止您为所有种类的特定方案(涵盖从分析类型到单个二进制文件仪表化的所有方案)创建数以百计的不同性能会话文件。我还将提一下“General”选项卡上的最后一个项,它具有一个非常诱人的名称 —“Managed Allocation Profiling”,相信这会使您感到更加好奇。在我讨论完常规分析之后,我将返回到该项。
“Performance Session”属性页上的另一个有趣的选项卡是“Sampling”选项卡(请参见图 3)。在这里,您可以告诉 EPT 您要执行哪种类型的采样。正如我在前面提到的那样,您对于希望如何进行采样具有非常好的控制。
图 3 各种 EPT 采样计数器选项
在执行分析运行时,EPT 会在二进制文件在硬盘中所处的位置上将其仪表化。如果您希望将仪表化的二进制文件移动到另一个位置,请选择“Performance Session”属性页中的“Binary”选项卡,然后选中“Relocate Instrumented Binaries”(它与 REBASE 样式的重定位绝对没有任何关系),并且指定您希望将更改后的二进制文件移至何处。
“Instrumentation”选项卡使您可以指定希望在仪表化发生之前和之后运行的程序。如果您需要对仪表化的二进制文件执行其他任务(例如,将其移动到全局程序集缓存中或 Web 服务器上的特定位置),则该选项卡可能很有用。“Advanced”选项卡在该测试版中未公开。最后,通过“Counters”选项卡,您可以告诉 EPT 从系统的 CPU 中收集其他数据,例如,L2 或 L3 缓存读取不中。显然,这些选项是只有少数开发人员才会需要的非常高级的选项,但是如果您确实需要它们,那么它们可以发挥巨大的作用。
在我继续讨论查看采样数据之前,我想提一下,“Performance Explorer”窗口可以根据您的需要打开任意多个性能会话。当您希望观察特定的前后方案,或者希望用不同的仪表化二进制文件执行单独的测试运行时,这一点极为有用。当您打开多个性能会话时,应当确保右键单击特定的会话,选择“Set as Current Session”以便让该会话的设置执行,然后将报告归档到它的报告节点中。
查看分析器数据
将性能会话设置为您希望执行的操作以后,就可以启动分析了。我将首先对 Animated Algorithm 执行采样分析,以查看我是否可以找到一些热点。从采样中获得良好数据的关键在于执行较长时间的运行。对于 Animated Algorithm,我会将 15 个排序算法中的每一个算法运行两次,并将采样设置为默认的一百万个时钟周期。
在完成某个运行之后,EPT 会将该运行的报告放到性能会话的“Reports”文件夹中。EPT 在运行期间收集原始性能数据,并将其流式传输到报告文件中(不做任何分析)。这样,您可以在运行应用程序时避免所有系统开销,但是您将为大型报告文件付出代价。我刚才完成的运行的采样报告文件大小为 3.70MB,它用了大约三分钟才完成。请确保您在运行 EPT 时具有大量的磁盘空间。
所有数据分析(它必然伴有调用堆栈的生成以及性能数字的计算)都在您打开报告文件时发生。对于测试版,在打开文件时速度可能会降低。看起来视图好像处于无限循环中,但是,如果进度栏正在报告窗口中移动,那么请您耐心一些,文件最终将弹出。
任何分析运行中的第一个视图是“Performance Report Summary”,它显示在刚刚完成的 Animated Algorithm 采样运行的图 4 中。不出所料,采样将发生在整个应用程序中,因此您正在查看的信息也就是您将在应用程序中看到的内容:大部分工作都发生在框架类库或操作系统内部。如果您确实在采样“Summary”视图中看到了您的一个方法,则您很可能看到了一个性能问题。
图 4 EPT 采样性能报告摘要
快速浏览一下图 4,您可能想知道 Inclusive Sampled 和 Exclusive Sampled 之间的区别。Exclusive Sampled 意味着该方法在取样时位于堆栈的顶部。换句话说,它是当前正在执行的函数。Inclusive Sampled 意味着该函数在取样时出现在调用堆栈中。因而,包含方法是当前正在执行的方法的调用方。
在采样方案中,一个方法在调用堆栈 (Inclusive Sampled) 中出现的次数越多,该函数在执行中花费的时间就越多,因此这里是您需要重点关注以进行性能调整的地方。对于 Exclusive Sampled 函数而言,函数在那里频繁出现表明该函数正在被频繁地调用,但是它的执行实际上可能非常快速。对于像 Animated Algorithm 这样需要进行大量图形处理的应用程序,我完全能够预料到 GDIPLUS.DLL 中的某个函数将靠近刚刚显示的列表的顶部。在图 4 中,位于 GDIPLUS.DLL 中偏移量 0x5B8D 处的函数(它恰好是 FLOOR 函数)被一直调用,以便计算在屏幕上的哪个位置显示某些内容。当您观察性能运行时,请确保设置符号服务器以获得可能存在的最佳信息。在撰写本文时,我使用了 EPT 的未发布版本,因而符号尚不可用。
在我跳到其他视图中以前,我希望仪表化 Animated Algorithm,并且完成与我针对采样分析器完成的运行相同的运行,以便显示仪表化运行的性能报告摘要。正如您可以猜到的那样,仪表化的运行会生成比采样运行多得多的数据。对于该运行,我仪表化了 Animated Algorithm 中的全部五个程序集,并最终得到一个 375MB 大小的会话文件。
采样和仪表化数据之间的主要区别是:采样查看整个进程空间,并且将显示框架类库或操作系统内部(换句话说,就是您在其中不具有源代码的位置)的调用。另一方面,仪表化只查看应用程序以及您在非仪表化模块上直接调用的方法。例如,如果您具有一个“Hello World!”应用程序,并且它的 Main 只调用 Console.WriteLine,则您将获得 Main 中任何工作的计时信息以及 Console.WriteLine 长度的计时信息,但是您不会获得有关 Console.WriteLine 方法的任何详细信息。
图 5 显示了仪表化运行的性能报告摘要。第一个表“Most Called Functions”显示了频繁使用的函数。该表中的第一列被错误标记为时间;它实际上表示对该函数的调用次数。百分比列显示了对该特定函数进行的调用总次数所占的百分比。在大多数运行中,您将在这里看到框架类库或操作系统函数。如果您看到一些来自您自己的代码的函数,则您最好了解一下您为什么如此频繁地调用该特定函数。
图 5 仪表化运行的摘要
“Functions with Most Individual Work”表列出了那些花费大部分时间以仅仅执行该函数(没有任何其他函数调用)的方法。这也称为该函数的独占时间。对于测试版本,“Time”列的单位为时钟走格数。对于最终版本,单位将是毫秒。但是,我认为性能运行的实际原始单位对于分析没有用。最重要的数字是百分比。在观察性能问题时,您希望知道,与应用程序中的所有其他方法相比,哪个方法占用了最长的时间。您在观察像 3519639455 和 3492589504 这样的两个数字时,很难对它们进行什么比较。幸运的是,该表包含百分比,而我对 EPT 团队的建议是从图表中丢弃原始数据。
最后一个表“Functions Taking Longest”显示方法的实际时间(也称为跑表时间或运行时间)。分析器记录方法的入口点时间和出口点时间,并将这两个值相减。该数字涵盖了被调用的所有子方法、所有上下文切换以及该方法执行的休眠。在图 5 中,您可以看到 System.Windows.Forms.Application.Run 占用了最长时间,就像您对 Windows 窗体应用程序所预料的那样。尽管很多开发人员将注意力集中于独占时间,但这只是整个性能状况的一小部分。如果方法正在对数据库进行调用或者进行 Web 服务调用,则您的方法在运行时所在的线程将在等待这些调用返回数据时阻塞,从而使得该线程从 CPU 中被移走。通过密切关注方法的运行时间,您可以找到代码中正在降低应用程序运行速度的部分。
尽管摘要视图很不错,但您最感兴趣的将是查看代码在何处阻塞了系统的其余部分(对于采样运行而言),或者阻塞了应用程序的其他方法(对于仪表化运行而言)。这是“Function”视图的职责范围 — 通过单击报告窗口底部的“Function”按钮可以选择该视图。您还可以双击“Summary”视图的任何方法以跳至“Function”视图。
对于采样运行,“Function”视图显示了至少一个包含捕获中所有函数捕获的列表。对于仪表化运行,您将看到作为该运行的一部分调用的所有仪表化方法。无论您正在执行哪种类型的分析,都会在“Function”视图中显示很多数据,因此您可以对代码的状况有一点儿感觉。
默认情况下,采样“Function”视图显示“Inclusive Samples”列和“Exclusive Samples”列。由于我喜欢百分比数字,因此我右键单击了列标题以向列标题中添加“Inclusive Percent”和“Exclusive Percent”。如果您要对多进程系统进行采样,则可能希望包含其他列(例如,“Process Name”或“Process ID”),以便您可以标识哪个方法采样与哪个进程相配。您还可以在仪表化“Function”视图中设置列标题,但是您将具有不同的标题组以供选择。
在“Function”视图中分析采样运行时,我喜欢首先扫一眼“Function”视图的头几个按“Inclusive Samples”列排序的页,以了解正在执行的方法。如果我在头几个页中没有看到我的任何方法,则我会右键单击“Function”视图并选择“Group by Module”,以便获得树报告视图。当您将函数按模块分组时,按特定列排序可以正确执行 — 这是一项很不错的功能。
对于仪表化运行,“Function”视图具有更多要显示的列。如果您拥有一台 40 英寸的显示器,则无需最大化 Visual Studio .NET 窗口就应当能够看到所有这些列。对于我们中的其他人而言,查看“Function”视图的最佳方式是按 Alt + Shift + Enter 以切换到全屏幕模式。
在这些列中,“Function”视图中的仪表化运行使用我在前面解释过的“包含”和“独占”术语。但是,还有另一个使人混淆的术语:应用程序。正如我提到的那样,运行时间是从一个仪表化点到另一个仪表化点的总时间,而不管该线程可能进行了哪些上下文切换。应用程序时间的思想是 EPT 将提取出在这些上下文切换中所花费的时间,以便您可以看到您的代码在 CPU 中实际执行的时间。图 6 列出了您将在仪表化“Function”视图中看到的不那么明显的列的定义。您可能希望将它传送到显示器上,直到 EPT 的联机帮助问世。
在观察仪表化运行的“Function”视图时,我添加了这些列以查看各种计时的百分比值,移除原始数字时间列,并且添加了两个转换列。这为我提供了有关该运行的更清晰视图。我在排序时所依据的第一个列是“% Application Exclusive Time”,因为我希望看到哪个函数正在完成大部分工作。由于仪表化在方法进行的所有子调用周围放入了探测,所以您完全有可能在该列表的顶部看到框架类库或操作系统。实际上,对于我的 Animated Algorithm 运行,System.Drawing.SolidBrush.ctor 和 System.Drawing.Brush.Dispose 在 Application Exclusive Time 百分比中被列为第一和第二,其百分比分别为 14.982% 和 14.867%。我编写的第一个函数是位于第三位的 Bugslayer.SortDisplayGraph.SorterGraph.UpdateSingleFixedElement(其百分比为 12.217%),它在图形中绘制单独的条。根据应用程序类型的不同,我在查看“Function”视图时可能会选择按其他列排序。如果存在 Web 服务或数据库调用,则我将查看 % Elapsed Inclusive Time,以便可以看到是否有特定方法卷入到长时间阻塞中。对于像 Animated Algorithm 这样的应用程序,我还将查看 Application Inclusive Time 的百分比。
基于我的仪表化运行中的上述数字,我很想查明是谁在对 SolidBrush 方法进行这些调用,因此我右键单击 .ctor 方法并选择“Show in Caller/Callee”视图,以便查看是谁在调用该方法。该视图(它对于采样分析也可用)使您一眼就可以看出目标方法的所有调用方,以及该目标方法调用的所有方法。
因为 .ctor 方法没有仪表化,所以“Caller/Callee”视图将不会显示任何被调用方,但是它显然会显示调用方。我双击了这个唯一的调用方,它恰好是具有第三高 Application Exclusive Time 并具有图 7 所示视图的 UpdateSingleFixedElement 方法。
在图 7 中,位于视图中部的下拉组合框是目标方法(在本例中为 UpdateSingleFixedElement)。方法上方的网格包含了目标方法的所有调用方(调用方)。目标方法下方的网格包含了目标方法调用以完成其工作的所有方法(被调用方)。如果您希望查看是谁调用了特定调用方,请双击该调用方方法,该方法将变为目标方法,并且您将看到原始目标方法下降到被调用方部分中。实质上,您只是将堆栈遍历了一遍。
仅仅基于图 7 中的视图,您就可以辨别出潜在的性能问题。Animated Algorithm 似乎不具有任何突出的性能问题,但是 SolidBrush .ctor 和 Dispose 占用了如此多的时间并且都在 UpdateSingleFixedElement 方法内部调用(调用了 351,872 次),这个事实表明我做了一件愚蠢的事情 — 我每次都通过该函数创建画笔,而实际上应该将其缓存。当我在将来的某一期 MSDN Magazine 中开始用 EPT 分析代码时,您还将看到 Animated Algorithm 的其他一些问题。
数据的最后一个常用视图是“Callstack”视图。在这里,您可以通过更具层次性的方式看到您在“Caller/Callee”窗口中观察到的调用堆栈。对于采样运行,您将在“Callstack”视图的顶层看到很多的条目,因为这些条目中的每一个都代表一个包含独占样本的唯一点。当您在采样运行中展开项时,您还将看到,在相同级别偶尔会存在一些项,这些项指示位于根部的函数具有多个引向它的调用树。根位置中显示的项是栈顶。
对于仪表化运行,“Callstack”窗口将具有与应用程序中的每个线程相对应的根元素。因为 Animated Algorithm 只有两个线程,所以您只能在树根级别看到两个项。在“Callstack”视图中,您可以看到绝对调用堆栈(从仪表化的第一个方法向下到最后一个方法),因此您可以真正了解应用程序的执行方式。我已经有很多次对我认为代码所完成的工作和代码实际上完成的工作之间的差异感到吃惊。
您可以花费大量时间在“Callstack”窗口中分析代码。当通过应用程序观察特定的踪迹时,您可以通过选择感兴趣的特定节点,向下移动,右键单击,并选择“Set Root”菜单选项,来消除大量噪音。在图 8 中,我希望查看 NSort.SwapSorter.Sort 进行的所有调用,因此将它设置为根可以消除 UI 线程的影响。
在将来的某一期中,我将更详细地讨论 EPT 显示区域中的最后两个选项卡:“Trace”和“Type”。在“Type”视图中,您可以观察已经在应用程序中分配的对象。它在测试版中有效。当我在前面讨论性能会话属性时,我提到过在“General”选项卡上有一个“Managed Allocation Profiling”部分。如果您选择“Allocations-only”单选按钮,则 EPT 会填充“Type”视图。在测试版中,报告看起来类似于其他许多工具中的报告,但是数据收集似乎不像在其他工具中那样具有如此之多的系统开销。最后,要了解 Enterprise Performance Tool 团队的想法以及有关该工具的更多信息,请确保在 blogs.msdn.com/profiler 查看他们的网络日记。
John Robbins 是 Wintellect 的创始人之一,该公司是一家专门致力于 Windows 和 .NET Framework 的软件咨询、教育和开发公司。他的最新著作是“Debugging Applications for Microsoft .NET and Microsoft Windows”(Microsoft Press, 2003)。要联系 John,请访问 www.wintellect.com。