【问题标题】:Function profiling woes - Visual Studio 2010 Ultimate函数分析问题 - Visual Studio 2010 Ultimate
【发布时间】:2011-04-02 20:48:47
【问题描述】:

我正在尝试分析我的应用程序以监控函数在重构之前和之后的效果。我对我的应用程序进行了分析并查看了摘要,我注意到Hot Path 列表没有提到我使用的任何函数,它只提到了 Application.Run() 之前的函数

我对分析还很陌生,想知道如何获得有关热路径的更多信息,如 MSDN documentation 所示;

MSDN 示例:

我的结果:

我注意到在输出窗口中有很多与加载符号时失败有关的消息,其中一些在下面;

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

(使用代码工具格式化,因此可读)

感谢任何指点。

【问题讨论】:

    标签: c# visual-studio-2010 refactoring profiling


    【解决方案1】:

    摘要视图上显示的“热路径”是最昂贵的调用路径,基于包含样本(来自函数的样本以及来自函数调用的函数的样本)和排他样本(仅来自函数的样本)的数量)。 “样本”只是当分析器的驱动程序捕获堆栈时函数位于堆栈顶部的事实(这发生在非常小的时间间隔内)。因此,函数的样本越多,执行的次数就越多。

    默认情况下,采样分析会启用一个名为“Just My Code”的功能,该功能会在堆栈中隐藏来自非用户模块的函数(它将显示深度为 1 个非用户函数如果由用户函数调用;在您的情况下为Application.Run)。来自未加载符号的模块或来自已知来自 Microsoft 的模块的函数将被排除在外。您在摘要视图上的“热门路径”表明最昂贵的堆栈没有任何分析器认为是您的代码的内容(Main 除外)。 MSDN 中的示例显示了更多功能,因为PeopleTrax.*PeopleNS.* 功能来自“用户代码”。单击摘要视图上的“显示所有代码”链接可以关闭“仅我的代码”,但我不建议在此处这样做。

    查看摘要视图中的“最个性化工作的功能”。这将显示具有最高独占样本计数的函数,因此,根据分析方案,调用成本最高的函数。你应该在这里看到更多你的函数(或你的函数调用的函数)。此外,“Functions”和“Call Tree”视图可能会显示更多详细信息(报告顶部有一个下拉菜单可以选择当前视图)。

    至于您的符号警告,其中大部分是预期的,因为它们是 Microsoft 模块(不包括 System.Data.SQLite.dll)。虽然您不需要这些模块的符号来正确分析您的报告,但如果您在“工具 -> 选项 -> 调试 -> 符号”中选中“Microsoft 符号服务器”并重新打开报告,则应该加载这些模块的符号.请注意,第一次打开报告需要更长的时间,因为需要下载和缓存符号。

    关于未能将符号序列化到报告文件中的另一个警告是文件无法写入的结果,因为它被阻止写入的其他东西打开。符号序列化是一种优化,它允许分析器在下次分析时直接从报告文件中加载符号信息。如果没有符号序列化,分析只需要执行与第一次打开报表时相同的工作量。

    最后,您可能还想尝试instrumentation,而不是在分析会话设置中进行采样。 Instrumentation 会修改您指定的模块,以在每个函数调用上捕获数据(请注意,这可能会导致更大的 .vsp 文件)。插桩非常适合关注特定代码段的时序,而采样对于一般的低开销分析数据收集来说是理想的。

    【讨论】:

    • 仪器似乎更符合我的需要,我可以确切地看到每个函数花费了多长时间。再次感谢!
    • @Peter Huene:有点跑题了,但我很好奇是否有可能在一次运行中同时获得本机代码和 .net 代码的源代码覆盖率信息。我的主要 exe 是使用 .net dll 的本机 .exe
    • @Chubsdad:是的。如果您使用的是 VS 2010 或更早版本,则需要使用 VSInstr 检测每个可执行文件,包括本机和托管,并使用 VSPerfMon 进行收集。 2012 年,代码覆盖工具 (CodeCoverage.exe) 将即时检测本机和托管可执行文件(在内存中),前提是它们的 .pdb 在收集时存在。
    【解决方案2】:

    如果我谈一谈分析,什么有效,什么无效,你介意吗?

    让我们编写一个人工程序,其中一些语句正在做可以优化掉的工作 - 即它们并不是真正必要的。 它们是“瓶颈”。

    子程序foo 运行一个需要一秒钟的 CPU 密集型循环。 还假设与其他所有指令相比,子程序 CALL 和 RETURN 指令花费的时间微不足道或为零。

    子程序 bar 调用 foo 10 次,但其中 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,您会看到对foo 的10 次调用,您会发现其中9 次是不必要的。问题解决了。

    如果您没有猜到,而分析器只是向您显示包含每个例程的样本百分比,您会看到:

    main 100%
    bar  100%
    foo  100%
    A    10%
    B    10%
    ...
    J    10%
    

    这告诉您查看mainbarfoo。你看mainfoo 是无辜的。你看看bar 调用foo 的地方,你就会发现问题,所以它已经解决了。

    如果除了显示函数之外,还可以显示调用函数的行,那就更清楚了。这样,无论源文本的函数有多大,您都可以找到问题所在。

    现在,让我们更改foo 使其执行sleep(oneSecond) 而不是受CPU 限制。这会如何改变事情?

    这意味着挂钟仍然需要 100 秒,但 CPU 时间为零。在仅 CPU 的采样器中采样将显示什么都没有

    因此,现在您被告知尝试使用仪器而不是采样。在它告诉你的所有内容中,它还告诉你上面显示的百分比,所以在这种情况下你可以找到问题,假设bar不是很大。 (编写小函数可能是有原因的,但满足分析器应该是其中之一吗?)

    实际上,采样器的主要问题是它无法在sleep(或 I/O 或其他阻塞)期间采样,并且它不会显示代码行百分比,只显示函数百分比。

    顺便说一句,1000 个样本可以为您提供非常精确的百分比。假设您采集的样本较少。您实际上需要多少才能找到瓶颈?好吧,由于 90% 的时间瓶颈都在堆栈上,如果你只取 10 个样本,它会在大约 9 个样本上,所以你仍然会看到它。 如果你只取了 3 个样本,它出现在其中两个或更多样本上的概率是 97.2%。**

    当您的目标是找到瓶颈时,高采样率被高估了。

    无论如何,这就是我依赖random-pausing的原因。

    ** 我是如何获得 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 + .729 = .972

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-17
      • 2011-07-29
      相关资源
      最近更新 更多