我最近发现可以使用编译器包将 JIT(即时)编译与 R 一起使用(我在最近的一篇博文中总结了我在这个主题上的发现)。
我被问到的问题之一是:
有什么陷阱吗?听起来好得令人难以置信,只需输入一行代码即可。
环顾四周后,我发现一个可能与 JIT 的“启动”时间有关的问题。但是在使用 JIT 时还有其他需要注意的问题吗?
我想R的环境架构会有一些限制,但我想不出一个简单的例子来说明我的头顶上的问题,任何建议或危险信号会有很大帮助吗?
我最近发现可以使用编译器包将 JIT(即时)编译与 R 一起使用(我在最近的一篇博文中总结了我在这个主题上的发现)。
我被问到的问题之一是:
有什么陷阱吗?听起来好得令人难以置信,只需输入一行代码即可。
环顾四周后,我发现一个可能与 JIT 的“启动”时间有关的问题。但是在使用 JIT 时还有其他需要注意的问题吗?
我想R的环境架构会有一些限制,但我想不出一个简单的例子来说明我的头顶上的问题,任何建议或危险信号会有很大帮助吗?
使用 rpart 进行简单测试的输出可能是在所有情况下都不要使用 enableJIT 的建议:
library(rpart)
fo <- function() for(i in 1:500){rpart(Kyphosis ~ Age + Number + Start, data=kyphosis)}
system.time(fo())
#User System verstrichen
#2.11 0.00 2.11
require(compiler)
enableJIT(3)
system.time(fo())
#User System verstrichen
#35.46 0.00 35.60
有什么解释吗?
上面给出的rpart
示例似乎不再是问题:
library("rpart")
fo = function() {
for(i in 1:500){
rpart(Kyphosis ~ Age + Number + Start, data=kyphosis)
}
} system.time(fo())
# user system elapsed
# 1.212 0.000 1.206
compiler::enableJIT(3)
# [1] 3
system.time(fo())
# user system elapsed
# 1.212 0.000 1.210
我还尝试了许多其他示例,例如
mean
虽然我并不总是得到加速,但我从未经历过明显的减速。
R> sessionInfo()
R version 3.3.0 (2016-05-03)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 16.04 LTS
原则上,一旦字节码被编译和加载,它的解释速度应该至少与原始 AST 解释器一样快。一些代码将受益于大幅加速,这通常是具有大量标量操作和循环的代码,其中大部分时间都花在了 R 解释上(我已经看到了 10 倍加速的示例,但任意微基准确实可以根据需要夸大这一点)。一些代码将以相同的速度运行,这通常是很好矢量化的代码,因此几乎不花时间在解释上。现在,编译本身可能很慢。因此,当即时编译器猜测它不会得到回报时,它现在不会编译函数(并且启发式随着时间的推移而变化,这已经在 3.4.x 中了)。启发式方法并不总能猜对,因此可能会出现编译无法获得回报的情况。
包可以在安装时进行字节编译,这样编译成本就不会在运行时(重复)支付,至少对于提前知道的代码是这样。这现在是 R 开发版本中的默认设置。虽然加载已编译代码比编译它要快得多,但在某些情况下,甚至可能加载不会执行的代码,因此实际上可能会有开销,但总体而言预编译是有益的。最近对 GC 的一些参数进行了调整,以减少加载不会执行的代码的成本。
我对包编写者的建议是使用默认值(即时编译现在在发布版本中默认启用,包安装时的字节编译现在在开发版本中启用)。如果您发现字节码编译器性能不佳的示例,请提交错误报告(我也看到过涉及rpart
早期版本的案例)。我建议不要使用代码生成和代码操作,尤其是在热循环中。这包括在闭包捕获的环境中定义闭包、删除和插入绑定。绝对不应该做eval(parse(text=
在热循环中(如果没有字节编译,这已经很糟糕了)。使用分支总是比动态生成新的闭包(没有分支)更好。此外,最好用循环编写代码,而不是用巨大的表达式(没有循环)动态生成代码。现在使用字节码编译器,现在通常可以在 R 中编写在标量上运行的循环(性能不会像以前那么差,所以人们可以更频繁地摆脱对性能关键部分切换到 C 的情况) .
继上一个答案之后,实验表明问题不在于循环的编译,而在于闭包的编译。[enableJIT(0) 或 enableJIT(1) 使代码保持快速,enableJIT(2) 显着减慢它,并且 enableJIT(3) 比前一个选项稍快(但仍然非常慢)]。同样与 Hansi 的评论相反, cmpfun 在类似程度上减慢了执行速度。