防止R中的性能回归

had*_*ley 32 testing performance r

检测R包中的性能回归有什么好的工作流程?理想情况下,R CMD check当我在代码中引入显着的性能回归时,我正在寻找可以集成的东西.

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

Ite*_*tor 17

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

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

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

我的步骤:

  1. 标准化平台(例如,一台或几台机器,特定虚拟机,或虚拟机+特定基础架构,亚马逊的EC2实例类型).
  2. 标准化将用于速度测试的数据集.
  3. 创建脚本并修复中间数据输出(即保存到.rdat文件),这些输出涉及非常少的数据转换.我的重点是某种建模,而不是数据操作或转换.这意味着我想为建模功能提供完全相同的数据块.但是,如果数据转换是目标,那么请确保预转换/操作数据尽可能接近不同版本包的测试标准.(有关记忆,缓存等的示例,请参阅此问题,可用于标准化或加速非焦点计算.它通过OP引用几个包.)
  4. 多次重复测试.
  5. 相对于固定基准来缩放结果,例如执行线性回归的时间,对矩阵进行排序等.这可以允许基础设施中的"本地"或瞬态变化,例如可能由于I/O,存储系统,依赖包裹等
  6. 尽可能严格地检查分析输出(有关一些见解,请参阅此问题,也可参考OP中的工具).

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

    不幸的是,我没有答案.

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

    对我来说,它与一般动态代码测试非常相似:输出(在这种情况下的执行时间)是可重现的,最佳的和透明的吗?透明度来自于了解影响整体时间的因素.这是Mike Dunlavey的建议很重要的地方,但我更倾向于使用直线轮廓仪.

    关于行分析器,请参阅我之前的问题,其中涉及Python和Matlab中的选项以获取其他示例.检查时钟时间是最重要的,但跟踪内存分配,执行行的次数以及调用堆栈深度也非常重要.

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

    几乎所有其他语言都有更好的工具.:)像Python和Matlab这样的解释语言有很好的和可能熟悉的工具示例,可以用于此目的.虽然动态分析非常重要,但静态分析可以帮助确定可能存在严重问题的位置.例如,Matlab有一个很棒的静态分析器,可以报告对象(例如矢量,矩阵)何时在循环内生长.仅通过动态分析找到它是很糟糕的 - 你已经浪费了执行时间来发现这样的事情,并且如果你的执行上下文非常简单(例如只是几次迭代或小对象),它并不总是可辨别的.

    至于语言无关的方法,你可以看看:

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

    它是可以建立在最高单元测试上的,还是通常单独完成的?

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


更新1:如果我可能如此大胆,那么我建议包括另一个问题,即:"比较两个版本的软件包的执行时间有什么问题?" 这类似于假设实现相同算法的两个程序应该具有相同的中间对象.这并不完全正确(看到这个问题 - 不是说我在推广我自己的问题,在这里 - 让事情变得更好,更快更难......导致关于这个主题的多个SO问题:)).以类似的方式,由于除了实现之外的因素,相同代码的两次执行可以在时间上不同.

因此,可能会出现一些问题,无论是在同一语言内还是跨语言,在同一个执行实例中,或者在"相同"实例中,这可能会影响运行时:

  1. 垃圾收集 - 不同的实现或语言可以在不同情况下进行垃圾收集.这可以使两个执行看起来不同,但它可能非常依赖于上下文,参数,数据集等.GC-obsessive执行看起来会更慢.
  2. 在磁盘,主板(例如L1,L2,L3高速缓存)或其他级别(例如,memoization)的级别进行高速缓存.通常,第一次执行将支付罚款.
  3. 动态电压调节 - 这很糟糕.当出现问题时,这可能是最困难的事情之一,因为它可以迅速消失.它看起来像缓存,但事实并非如此.
  4. 任何你不了解的工作优先经理.
  5. 一种方法使用多个内核,或者做一些关于如何在内核或CPU之间分配工作的聪明的东西.例如,在某些情况下,将进程锁定到核心可能很有用.在这方面,一个R包的执行可能更幸运,另一个包可能非常聪明......
  6. 未使用的变量,过多的数据传输,脏缓存,未刷新的缓冲区......列表继续.

关键结果是:理想情况下,我们应该如何测试预期值的差异,这取决于订单效应产生的随机性?嗯,非常简单:回到实验设计.:)

当执行时间的经验差异与"预期"差异不同时,能够进行额外的系统和执行监控非常棒,这样我们就不必重新运行实验,直到我们面对蓝色.


Dir*_*tel 10

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

然后使用单元测试相似的框架,并将"必须在X单位时间内完成"作为另一个要实现的测试标准.换句话说,做一些像

 stopifnot( timingOf( someExpression ) < savedValue plus fudge)
Run Code Online (Sandbox Code Playgroud)

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

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