剪切I/O边界耙子任务评估时间缩短12.4%

你经常跑步吗关于拥有数千名员工的项目文件任务s在这种情况下,您的Rake执行时间很可能是输入输出绑定。我创建了两个可以剪切的补丁⅛ 离开你的耙子任务评估/编译时间。

Rake是Ruby编程语言的任务和构建自动化工具。它作为Ruby标准库默认工具和模块集的一部分分发。它是一个制作-类似于只增量重建项目中已更改部分的工具。它通过在文件系统中查询项目中每个源文件和对象文件最后修改的时间戳来跟踪更改。每个查询都需要对每个文件的操作系统内核进行单独的系统调用(syscall)。

在现代快速存储设备上,系统调用是廉价的。然而,小数字加起来很快。即使看似即时的系统调用在执行数千次时也会对性能产生负面影响。

Ruby总是生成全局虚拟机锁(GVL),从而为每个调用的方法生成Rake的每任务线程执行rb_stat()(包括文件存在吗?File.mtime)。生成I/O绑定线程是有意义的;它使处理器能够花时间做一些比等待系统调用更有用的事情。然而,过度的上下文切换会使处理器浪费更多的时间,而不是通过不必要地将内容移入和移出不同的CPU缓存级别和RAM来节省时间。

Rake不能取消查询文件修改时间戳的系统调用。它们是软件功能的关键部分。当然,我假设检查修改时间戳比每次运行时重新编译源代码要便宜,即使它们没有更改。

我分析了一个Rake项目,当时我注意到Rake对每个文件查询文件系统的频率比我预期的要高。在我的项目中,它累积了数万个不必要的系统调用。假设问题出在我在项目中使用的任务生成器的某个地方,我就浪费了好几个小时。

当我最终将注意力转向Rake本身时,我花了一分钟来识别和修复这个问题。Rake查询文件系统,查看项目中的每个对象文件是否都存在。任务文件现在还不存在的,去建造吧。但是,对于存在的文件,Rake会查询它们是否存在再一次然后再次查询以获取文件的修改时间戳。

检查文件是否再次存在没有意义。此检查是项目中每个文件所需的另一个系统调用。相反,Rake应该在检查文件的修改时间时假设文件存在,并捕获如果文件不存在可能出现的任何错误。这种方法还使查询成为一种原子(自包含)操作(文件可能在检查其存在性和修改时间之间消失)。

睡了一个好觉之后,我意识到我也可以摆脱另一个文件存在检查。单个文件修改时间检查系统调用足以提供所有必需的信息。

我已经提出了这两项修改到Rake项目。这是两个简单的重构补丁,可以消除不必要的系统调用。然而,在上游接受补丁并在Rake中可用之前可能需要一段时间。如果您现在不耐烦,想加快Rake的速度,可以下载并应用我的补丁(f8afda2b22.0补丁abf5e26464.0修补程序).

我经常在包含数万个文件的项目上运行Rake。在Rake完成评估运行之间需要重新编译的文件时,我会花费大量时间。在项目没有更改的情况下,这些修补程序将我的构建时间缩短了12.4%。这是在高性能计算机配置上的快速M.2 SSD上运行的测试。您可以通过较慢的存储后端或计算机,可以看到更大的性能提升。在某些环境中,与存储相关的系统调用速度较慢 — 我看着你们,Windows Linux子系统(WSL2)! — 将受益匪浅。

相关阅读