【问题标题】:Preventing performance regressions in R防止 R 中的性能回归
【发布时间】:2012-01-17 21:36:03
【问题描述】:

在 R 包中检测性能回归的良好工作流程是什么?理想情况下,我正在寻找与 R CMD check 集成的东西,当我在代码中引入显着的性能回归时会提醒我。

一般来说,什么是好的工作流程?还有哪些其他语言提供了好的工具?它是可以建立在顶级单元测试之上的,还是通常单独完成的?

【问题讨论】:

  • 棘手。您甚至可能没有像以前那样在同一台 PC 上运行测试,因此它必须相对于一些稳定的基准......
  • This method 适用于任何语言,包括 R。它所做的是准确指出需要时间的代码,并粗略估计所需时间的百分比。如果您发现所花费的时间发生了变化,或者其百分比显着增加,则说明回归是什么。
  • ...如果您确实确定了某些占用高百分比的东西,并且您修复了它,您会看到由于它而导致的百分比会下降,并且总时间也会下降。其他东西的百分比会上升,因为它占较小总数的较大部分。这是意料之中的。

标签: performance r testing


【解决方案1】:

这是一个非常具有挑战性的问题,也是我经常处理的一个问题,因为我在一个包中交换了不同的代码以加快处理速度。有时,性能回归伴随着算法或实现的变化,但也可能由于所使用的数据结构的变化而出现。

在 R 包中检测性能回归的良好工作流程是什么?

就我而言,我倾向于使用不同的固定数据集来加快速度的非常具体的用例。正如 Spacedman 所写,拥有一个固定的计算系统很重要,但这几乎是不可行的:有时共享计算机可能有其他进程,即使它看起来很空闲,它也会减慢 10-20% 的速度。

我的步骤:

  1. 标准化平台(例如,一台或几台机器、特定虚拟机或虚拟机 + 特定基础设施,例如 Amazon 的 EC2 实例类型)。
  2. 标准化将用于速度测试的数据集。
  3. 创建涉及极少数据转换的脚本和固定的中间数据输出(即保存到 .rdat 文件)。我的重点是某种建模,而不是数据操作或转换。这意味着我想为建模函数提供完全相同的数据块。但是,如果目标是数据转换,那么确保在不同版本的包的测试中,预先转换/操作的数据尽可能接近标准。 (有关可用于标准化或加速非焦点计算的记忆、缓存等示例,请参阅 this question。它引用了 OP 的几个包。)
  4. 多次重复测试。
  5. 相对于固定基准来衡量结果,例如执行线性回归、矩阵排序等的时间。这可能会导致基础设施的“局部”或瞬时变化,例如可能是由于 I/O、内存系统、依赖包等。
  6. 尽可能严格地检查分析输出(请参阅 this question 了解一些见解,同时参考 OP 中的工具)。

    理想情况下,我正在寻找与 R CMD 检查集成的东西,当我在代码中引入显着的性能回归时会提醒我。

    很遗憾,我对此没有答案。

    一般来说什么是好的工作流程?

    对我来说,它与一般的动态代码测试非常相似:输出(在这种情况下为执行时间)是否可重现、最优且透明?透明度来自于了解影响整体时间的因素。这就是 Mike Dunlavey 的建议很重要的地方,但我更喜欢更进一步,使用线路分析器。

    关于线分析器,请参阅my previous question,其他示例参考 Python 和 Matlab 中的选项。检查时钟时间最重要,但跟踪内存分配、行执行次数和调用堆栈深度也非常重要。

    还有哪些其他语言提供好的工具?

    几乎所有其他语言都有更好的工具。 :) 像 Python 和 Matlab 这样的解释语言有很好的和可能熟悉的工具示例,可以为此目的进行调整。尽管动态分析非常重要,但静态分析可以帮助确定可能存在严重问题的地方。例如,Matlab 有一个很棒的静态分析器,可以报告对象(例如向量、矩阵)何时在循环内增长。仅通过动态分析发现这一点是很糟糕的——你已经浪费了执行时间来发现这样的东西,而且如果你的执行上下文非常简单(例如,只有几次迭代或小对象),它并不总是可辨别的。

    至于与语言无关的方法,您可以查看:

    1. Valgrind 和 cachegrind
    2. 监控磁盘 I/O、脏缓冲区等
    3. 监控 RAM(Cachegrind 很有帮助,但您可以只监控 RAM 分配,以及有关 RAM 使用情况的大量详细信息)
    4. 使用多核

    它可以建立在顶级单元测试之上,还是通常单独完成?

    这很难回答。对于静态分析,它可以发生在单元测试之前。对于动态分析,可能需要添加更多测试。将其视为顺序设计(即来自实验设计框架):如果执行成本在某些统计变化允许范围内看起来是相同的,则不需要进一步的测试。但是,如果方法 B 的平均执行成本似乎高于方法 A,则应该执行更密集的测试。


更新 1:如果我这么大胆的话,我建议包括另一个问题,那就是:“比较一个包的两个版本的执行时间有哪些问题?”这类似于假设实现相同算法的两个程序应该具有相同的中间对象。这不完全正确(请参阅this question - 不是我在这里宣传我自己的问题 - 让事情变得更好更快是一项艰苦的工作......导致关于这个主题的多个 SO 问题:))。类似地,同一代码的两次执行可能由于执行以外的因素而在时间上有所不同。

因此,在同一语言或跨语言、同一执行实例或“相同”实例之间可能会出现一些问题,这会影响运行时:

  1. 垃圾收集 - 不同的实现或语言在不同情况下可能会遇到垃圾收集。这可能会使两个执行看起来不同,尽管它可能非常依赖于上下文、参数、数据集等。GC-obsessive 执行看起来会更慢。
  2. 在磁盘、主板级别(例如 L1、L2、L3 缓存)或其他级别(例如 memoization)缓存。通常,第一次处决会受到惩罚。
  3. Dynamic voltage scaling - 这个很烂。当出现问题时,这可能是最难找到的野兽之一,因为它可以很快消失。它看起来像缓存,但事实并非如此。
  4. 任何您不知道的工作优先级经理。
  5. 一种方法使用多个内核,或者对如何在内核或 CPU 之间分配工作进行一些巧妙的处理。例如,在某些情况下,将进程锁定到核心可能很有用。在这方面,一个 R 包的执行可能更幸运,另一个包可能非常聪明......
  6. 未使用的变量、过多的数据传输、脏缓存、未刷新的缓冲区……不胜枚举。

关键结果是:理想情况下,我们应该如何测试期望值的差异,受制于顺序效应造成的随机性?好吧,很简单:回到实验设计。 :)

当执行时间的经验差异与“预期”差异不同时,最好启用额外的系统和执行监控,这样我们就不必重新运行实验,直到我们脸色发青.

【讨论】:

    【解决方案2】:

    在这里做任何事情的唯一方法是做出一些假设。因此,让我们假设一台未更改的机器,否则需要“重新校准”。

    然后使用类似单元测试的框架,并将“必须在 X 个时间单位内完成”视为要满足的另一个测试标准。换句话说,做类似的事情

     stopifnot( timingOf( someExpression ) < savedValue plus fudge)
    

    所以我们必须将之前的时间与给定的表达式相关联。也可以使用来自三个现有单元测试包中的任何一个的平等测试比较。

    没有什么是 Hadley 无法处理的,所以我想我们几乎可以期待在下一次漫长的学术休息之后得到一个新的软件包 timr :)。当然,这必须是可选的,因为在“未知”机器上(想想:CRAN 测试包)我们没有参考点,否则软糖因子必须“转到 11”才能在新机器上自动接受.

    【讨论】:

    • 或者可能是测试的扩展? expect_that(foo(x),takes_less_than(bar(y)))?
    • bar(y) 是我们之前的 foo(x) 的函数,加上一些容忍/软糖措施?可能。
    • 不确定这是否连接,但svUnit在单元测试中捕获timing信息,并将其报告到日志中。
    • RUnit 也是如此。但如果我没记错的话,两者都只报告总数。
    【解决方案3】:

    最近在 R-devel 提要上宣布的一项更改可能会为此提供粗略的衡量标准。

    R-devel 实用程序的变化

    “R CMD 检查”可以选择性地报告检查各个部分的时间:这由“编写 R 扩展”中记录的环境变量控制。

    http://developer.r-project.org/blosxom.cgi/R-devel/2011/12/13#n2011-12-13

    可以检查运行测试所花费的总时间并与之前的值进行比较。当然,添加新测试会增加时间,但仍然可以看到显着的性能下降,尽管是手动的。

    这不像单个测试套件中的时间支持那么细粒度,但它也不依赖于任何一个特定的测试套件。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-02-11
      • 2012-01-16
      • 2011-10-14
      • 2018-04-28
      • 2023-03-11
      • 2021-07-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多