我最近发现可以使用编译器包使用JIT(及时)编译R(我总结了我在a recent blog post中关于这个主题的发现)。
我被问到的一个问题是:
有任何陷阱吗?这听起来好得令人难以置信,只需输入一行代码即可。
环顾四周后,我发现一个可能的问题与JIT的“启动”时间有关。但是在使用JIT时还有其他问题需要注意吗?
我想与R的环境架构有一些限制,但我想不出一个简单的问题,我的头脑,任何建议或危险信号将有很大的帮助?
上面给出的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
使用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
任何解释?
原则上,一旦编译和加载了字节码,它应该始终被解释为至少与原始AST解释器一样快。一些代码将受益于大的加速,这通常是具有大量标量操作和循环的代码,其中大部分时间用于R解释(我已经看到10x加速的示例,但是任意微基准确实可以根据需要对其进行充气)。一些代码将以相同的速度运行,这通常是代码很好地矢量化,因此几乎没有时间进行解释。现在,编译本身可能很慢。因此,即时编译器现在不会编译函数,因为它猜测它不会得到回报(并且启发式随时间变化,这已经在3.4.x中)。启发式并不总是猜对了,所以可能存在编译无法获得回报的情况。典型的有问题的模式是代码生成,代码修改和对闭包中捕获的环境的绑定的操纵。
软件包可以在安装时进行字节编译,这样就不会在运行时(重复)支付编译成本,至少对于提前知道的代码。现在这是R的开发版本中的默认值。虽然加载编译代码比编译它快得多,但在某些情况下,可能正在加载甚至不会执行的代码,因此实际上可能存在开销,但总体而言预编译是有益的。最近调整了GC的一些参数以降低加载不会执行的代码的成本。
我对包编写器的建议是使用默认值(默认情况下,在发布版本中,即时编译现已开启,包安装时的字节编译现在在开发版本中)。如果您找到字节码编译器效果不佳的示例,请提交错误报告(我在早期版本中也看到了涉及rpart
的案例)。我建议不要使用代码生成和代码操作,特别是在热循环中。这包括在闭包捕获的环境中定义闭包,删除和插入绑定。绝对不应该在热循环中执行eval(parse(text=
(这已经很糟糕,没有字节编译)。使用分支总是比动态生成新的闭包(没有分支)更好。使用循环编写代码比使用大表达式(没有循环)动态生成代码更好。现在使用字节码编译器,现在通常可以编写在R中的标量上运行的循环(性能不会像以前那样糟糕,所以人们可以更频繁地离开而无需切换到C以获得性能关键部分) 。
继前面的答案之后,实验表明问题不在于循环的编译,而在于闭包的编译。 [enableJIT(0)或enableJIT(1)保持代码快速,enableJIT(2)显着减慢代码,enableJIT(3)比前一个选项略快(但仍然非常慢)]。与Hansi的评论相反,cmpfun在相似程度上减缓了执行速度。