【问题标题】:Profiling algorithm分析算法
【发布时间】:2012-10-30 10:37:27
【问题描述】:

我需要实现执行时间测量功能。我想到了两种可能。

  • 第一次 - 常规 time() 调用,只需记住每个执行步骤开始的时间和每个执行步骤完成的时间。 Unix time shell 命令就是这样工作的。

  • 第二种方法是采样。每个执行步骤在执行开始之前设置某种标志(例如 - 在堆栈帧中创建一些对象),并在完成时将其销毁。计时器定期扫描所有标志并生成执行时间配置文件。如果某个执行步骤比其他执行步骤花费更多时间 - 它将被扫描更多次。许多分析器都是这样工作的。

我需要在我的服务器应用程序中添加一些分析功能,什么方法更好,为什么?我认为第二种方法不太准确,第一种方法增加了对分析库代码的依赖。

【问题讨论】:

    标签: c++ algorithm profiling


    【解决方案1】:

    第二种方法本质上是堆栈采样。 您可以尝试自己做,通过某种进入退出事件捕获,或者如果有一个实用程序来实际读取堆栈会更好。 后者的优势在于您可以获得代码行解析,而不仅仅是方法级别。

    很多人对此并不了解,那就是计时测量的精度远不如问题识别的精度重要

    即使在 I/O 或其他阻塞期间进行采样也很重要,因此您不会对不必要的 I/O 视而不见。如果您担心与其他流程的竞争可能会增加时间,请不要担心,因为真正重要的不是绝对时间测量,而是百分比。 例如,如果一行代码在堆栈上占挂钟时间的 50%,因此要负责这么多,那么无论发生什么其他事情,删除它都会使应用程序的速度加倍。

    分析不仅仅是获取样本。 人们通常对他们对他们的所作所为很随意,但这就是钱的所在。 首先,包含时间是方法或代码行在堆栈上的时间分数。忘记“自我”时间——它包含在包容性时间中。 忘记调用计数 - 它与包含百分比的关系充其量是非常间接的。 如果你在总结,最好的方法是有一个“蝴蝶视图”,它的重点是一行代码。 它的左右是堆栈样本上紧邻其上方和下方的代码行。 每行代码旁边都有一个百分比 - 包含该行代码的堆栈样本的百分比。 (不用担心递归。这根本不是问题。)

    比任何类型的总结更好的是让用户自己看到堆栈样本的一小部分随机选择。 这样,用户就可以全面了解每个快照的使用时间。 出现在多个样本上的任何可避免的活动都有可能实现一些严重的加速,这是有保证的。 人们经常认为“嗯,这可能只是侥幸,而不是真正的瓶颈”。 不是这样。修复它会得到回报,也许是一点点,也许是很多,但平均而言——意义重大。 人们不应该被风险规避所统治。

    More on all that.

    【讨论】:

      【解决方案2】:

      当 boost 是一个选项时,您可以使用timer library

      【讨论】:

        【解决方案3】:

        确保您真正了解您正在编写的分析器中要查找的内容,每当您收集某段代码的总执行时间时,它将包括在其所有子代码中花费的时间以及它可能很难真正找到系统中的瓶颈是什么,因为最顶层的函数总是会冒泡为最昂贵的函数——比如 main()。

        我的建议是挂钩每个函数的序言和尾声(如果您的应用程序是 CLR 应用程序,您可以使用 ICorProfilerInfo::SetEnterLeaveFunctionHooks 来做到这一点,您也可以在每个方法的开头使用宏,或者任何其他机制,将在每个函数的开头和每个函数中注入您的代码)并以树的形式为您分析的每个线程收集您的时间。

        这个算法看起来和这个有点相似:

        • 为您监控的每个线程创建一个类似堆栈的数据结构。
        • 每当您收到有关开始执行的函数的通知时,将可以识别该函数的内容推送到该堆栈中。

          • 如果该函数不是堆栈上的唯一函数,那么您知道尚未返回的前一个函数就是调用您的函数的函数。
          • 在您喜欢的数据结构中跟踪那些被调用者调用的关系。
        • 每当一个方法返回时,它的标识符将始终位于其线程堆栈的顶部。它的总执行时间等于(最后一个(它的)标识符被压入堆栈的时间 - 当前时间)。弹出堆栈的标识符。

        通过这种方式,您可以对占用执行时间的内容进行树状细分,您可以在其中查看子调用占函数总执行时间的原因。

        玩得开心!

        【讨论】:

          【解决方案4】:

          在我的分析器中,我使用了您提到的第一种方法的扩展版本。

          我有一个提供上下文对象的类。您可以在工作代码中将它们定义为自动对象,一旦执行流离开定义它们的上下文(例如,函数或循环),就会被释放。构造函数调用GetTickCount(它是一个Windows项目,你可以根据你的目标平台选择类似的函数)并存储这个值,而析构函数再次调用GetTickCount并计算这个时刻和开始之间的差异。每个对象都有唯一的上下文 ID(可以在同一上下文中自动生成为静态对象),因此分析器可以总结具有相同 ID 的所有时间,这意味着相同的上下文已经传递了多次。还计算了执行次数。

          这是一个用于预处理器的宏,它有助于分析函数:

          #define _PROFILEFUNC_  static ProfilerLocator locator(__FUNC__); ProfilerObject obj(locator);
          

          当我想分析一个函数时,我只需在函数的开头插入 PROFILEFUNC。这会生成一个静态对象locator,它标识上下文并将其名称存储为函数名称(您可以决定选择其他命名)。然后自动在堆栈上创建ProfilerObject 并“跟踪”自己的创建和删除,并将其报告给分析器。

          【讨论】:

          • 第二种方法是当您有专用线程定期扫描一些正在执行的上下文列表并为每个上下文维护计数器时。计数器值越多,上下文执行时间越长。
          • @Lazin,好吧,那我误解了你的想法。需要更正我的答案。
          猜你喜欢
          • 2011-10-29
          • 2014-10-28
          • 1970-01-01
          • 1970-01-01
          • 2013-09-23
          • 2012-05-24
          • 2011-07-28
          • 2010-12-20
          相关资源
          最近更新 更多