功能分析的困扰 - Visual Studio 2010 Ultimate

8
我正在尝试对我的应用程序进行剖析,以监视函数的效果,在重构之前和之后都要这样做。我已经对我的应用程序进行了分析,并查看了汇总信息。我注意到 热点路径 列表中没有提到我使用的任何函数,它只提到了 Application.Run() 函数。
我对剖析不太熟悉,想知道如何获取有关热点路径的更多信息,就像 MSDN 文档 所演示的那样; MSDN 示例: MSDN Example 我的结果: Hot Path Summary 我在输出窗口中注意到有许多与加载符号失败相关的消息,其中一些如下:
Failed to load symbols for C:\Windows\system32\USP10.dll.  
Failed to load symbols for C:\Windows\system32\CRYPTSP.dll.
Failed to load symbols for (Omitted)\WindowsFormsApplication1\bin\Debug\System.Data.SQLite.dll.
Failed to load symbols for C:\Windows\system32\GDI32.dll.  
Failed to load symbols for C:\Windows\WinSxS\x86_microsoft.windows.common-controls_6595b64144ccf1df_6.0.7601.17514_none_41e6975e2bd6f2b2\comctl32.dll.
Failed to load symbols for C:\Windows\system32\msvcrt.dll. 
Failed to load symbols for C:\Windows\Microsoft.NET\Framework\v4.0.30319\nlssorting.dll.
Failed to load symbols for C:\Windows\Microsoft.Net\assembly\GAC_32\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll.  Failed to load symbols for
C:\Windows\Microsoft.Net\assembly\GAC_32\System.Transactions\v4.0_4.0.0.0__b77a5c561934e089\System.Transactions.dll.
Unable to open file to serialize symbols: Error VSP1737: File could not be opened due to sharing violation: - D:\(Omitted)\WindowsFormsApplication1110402.vsp

感谢任何指导。
2个回答

13
在概要视图中显示的“Hot Path”是基于包含采样和独立采样数量(来自该函数和由该函数调用的函数)的路径中最昂贵的呼叫路径。一个“采样”只是这个函数在分析器驱动程序捕获堆栈时位于堆栈顶部的事实(这发生在非常小的时间间隔内)。因此,一个函数拥有的采样越多,它执行的次数就越多。
默认情况下进行采样分析时,启用了称为“Just My Code”的功能,它隐藏了来自非用户模块的堆栈上的函数 (如果被用户函数调用,则会显示1个非用户函数深度; 在您的情况下为 Application.Run)。来自未加载符号或已知来自 Microsoft 的模块的函数将被排除在外。在概要视图中,“Hot Path”表示最昂贵的堆栈没有任何来自分析器认为是您代码的内容(除了Main)。MSDN的示例显示了更多的函数,因为PeopleTrax.*PeopleNS.*函数来自“用户代码”。可以通过在总览视图上单击“显示所有代码”链接来关闭“只看我的代码”,但我不建议在这里这样做。
查看总览视图中显示的“执行最多任务的函数”。这显示具有最高独立采样计数的函数,因此根据分析方案,它们是调用成本最高的函数。你应该在这里看到更多你自己的函数(或被你自己的函数调用的函数)。此外,“函数”和“调用树”视图可能会向您显示更多细节 (报告顶部有一个下拉菜单可选择当前视图)。关于您看到的符号警告,大部分都是由于它们是Microsoft模块(不包括System.Data.SQLite.dll)。尽管您不需要这些模块的符号来正确地分析报告,但如果您在“工具->选项->调试->符号”中勾选了“Microsoft符号服务器”,并重新打开报告,这些模块的符号应该会被加载。请注意,第一次打开报告需要花费更长时间,因为需要下载和缓存符号。
另一个关于无法将符号序列化到报告文件中的警告是文件无法写入的结果,因为它被其他阻止写入的东西打开了。符号序列化是一种优化,允许分析器在下一次分析时直接从报告文件中加载符号信息。如果没有符号序列化,则分析只需要执行与第一次打开报告相同数量的工作。
最后,您还可以尝试在分析会话设置中使用“instrumentation”而不是采样。Instrumentation修改您指定的模块以捕获每个函数调用的数据(请注意,这可能会导致.vsp文件变得更大)。Instrumentation非常适合专注于特定代码片段的定时,而采样则非常适合收集低开销的通用性能数据。

仪表化似乎更符合我的需求,我可以准确地看到每个函数内部花费的时间。再次感谢! - Jamie Keeling
@Peter Huene:有点偏题,但我很好奇是否可以在单次运行中获取本机代码和.NET代码的源代码覆盖率信息。我的主要exe是一个本机.exe,它使用.NET dlls。 - Chubsdad
@Chubsdad:是的。如果你使用的是VS 2010或更早版本,你需要使用VSInstr来检测每个可执行文件(包括本地和托管代码),然后使用VSPerfMon进行收集。在2012年,代码覆盖工具(CodeCoverage.exe)将会在内存中实时检测本地和托管代码的可执行文件,只要它们的.pdb文件在收集时存在即可。 - Peter Huene

6

你介意我谈一下性能剖析,哪些东西有效,哪些无效吗?

假设有一个虚构的程序,其中一些语句正在执行可以优化掉的工作 - 即它们并不是真正必要的。这些语句是"瓶颈"。

子例程foo运行一个占用CPU时间的循环,需要1秒钟。同时假设子例程调用和返回指令与其他所有东西相比都需要微不足道的时间或零时间。

子例程bar调用foo10次,但其中9次是不必要的,你事先不知道也不能告诉,直到你的注意力被引向那里。

子例程ABC、...、J是10个子例程,它们每个都调用bar一次。

顶级例程main依次调用AJ

因此,总调用树如下:

main
  A
    bar
      foo
      foo
      ... total 10 times for 10 seconds
  B
    bar
      foo
      foo
      ...
  ...
  J
    ...
(finished)

需要多长时间?显然是100秒。

现在让我们看看性能分析策略。 在均匀间隔下,取样堆栈(例如,1000个样本)。

  1. 有自我时间吗?有。 foo 占据了100%的自我时间。 它是一个真正的“热点”。 这有助于找到瓶颈吗?不。因为它不在 foo 中。

  2. 什么是热点路径?好吧,堆栈样本看起来像这样:

    main -> A -> bar -> foo(100个样本,或10%)
    main -> B -> bar -> foo(100个样本,或10%)
    ...
    main -> J -> bar -> foo(100个样本,或10%)

有10个热点路径,但没有一个足够大,可以使您获得更快的速度。

如果你碰巧猜对了,并且如果分析器允许,你可以将bar作为你调用树的“根”。然后你会看到这个:

bar -> foo (1000 samples, or 100%)

如果你了解,你应该知道foobar各自独立负责100%的时间,因此它们是优化的关键。 首先你查看foo,但当然你知道问题不在那里。 然后你查看bar,你会发现有10个调用foo,其中9个是不必要的。 问题得到解决。
如果你没有猜测,而是简单地从性能分析器中显示每个例程包含的百分比样本,你将看到以下结果:
main 100%
bar  100%
foo  100%
A    10%
B    10%
...
J    10%

那告诉你查看 main, barfoo。你会发现 mainfoo 是无罪的。你查看了 bar 调用 foo 的地方,你就找到了问题,所以它得到了解决。
如果除了显示函数,还能显示调用函数的行号,那就更清晰了。这样,无论源文本中的函数有多大,都可以找到问题。
现在,让我们将 foo 更改为执行 sleep(oneSecond) 而不是 CPU 绑定。这将如何改变事情?
这意味着墙上时钟仍需要 100 秒,但 CPU 时间为零。在仅限于 CPU 的采样器中进行采样将一无所获。
因此,现在要尝试检测而不是采样。在所有提示中,它还告诉您上面显示的百分比,因此在这种情况下,您可以找到问题,假设 bar 不是很大。(也许有写小函数的理由,但是否满足分析器的要求之一?)
实际上,采样器最大的问题在于它不能在 sleep(或 I/O 或其他阻塞)期间进行采样,并且它不会显示代码行百分比,仅显示函数百分比。
顺便说一句,1000 个样本会给您带来看起来很精确的百分比。假设您采取的样本较少。实际上,您需要多少个样本才能找到瓶颈?由于瓶颈在堆栈上 90% 的时间,如果您只拿了 10 个样本,则其中有约 9 个样本是瓶颈,因此您仍然会看到它。 即使您只拿了 3 个样本,它出现在其中两个或更多个样本的概率也高达 97.2%。**
当您的目标是查找瓶颈时,高采样率被过分评价了。
无论如何,这就是为什么我依赖于 随机暂停
** 我怎么得出 97.2%?把它看作抛掷 3 次硬币,这是一个非常不公平的硬币,其中“1”表示看到瓶颈。有 8 种可能性:
       #1s  probabality
0 0 0  0    0.1^3 * 0.9^0 = 0.001
0 0 1  1    0.1^2 * 0.9^1 = 0.009
0 1 0  1    0.1^2 * 0.9^1 = 0.009
0 1 1  2    0.1^1 * 0.9^2 = 0.081
1 0 0  1    0.1^2 * 0.9^1 = 0.009
1 0 1  2    0.1^1 * 0.9^2 = 0.081
1 1 0  2    0.1^1 * 0.9^2 = 0.081
1 1 1  3    0.1^0 * 0.9^3 = 0.729

所以看到它2次或3次的概率是0.081*3 + 0.729 = 0.972。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接