利用即时编译(JIT)技能提高R的执行效率
从R 2.13以来,compiler包就成为了R默认安装的一部门。自R 2.14以来,所有的尺度函数与包都被预先编译为呆板码,因此获得了2倍或更多的效率晋升。Tal Galili的这篇文章就先容了利用comipler包加快R代码的执行的要领。此文由R客翻译自原文,省略了部门对JIT道理部门的先容,具体先容请参考原文。
什么是JIT(Just-In-Time compliation,即时编译)技能?
传统的计较机语言有两种执行方法:静态编译型,即代码执行前先被转换为呆板码;表明型,即一边对代码举办编译,一边执行。而JIT是这两种方法的殽杂,一边编译,一边执行,但同时也对部门已编译的代码举办缓存,以提高执行的效率。
R与JIT
到本日为止,有两个包支持R语言的JIT:jit包(通过Ra支持)与compiler包(R默认支持)。jit包是由 Stephen Milborrow建设的,它可以实现R中轮回语句的JIT来提高执行速度。但它必需通过一个非凡的R, “the Ra Extension to R“来执行。凡是的R来执行的话就会完全没有结果。这个jit包已经在2011年遏制了开拓。compiler包是R 2.13以来成为默认安装的一部门,它不能把R直接编译为最底层的呆板码,但可以把作为高层语言的巨大的R代码编译为低层的简朴的字节码。后者由于去掉了很多耗时的操纵,执行效率上要比前者高许多。compiler包大部门代码是用R写成的,少数是用C。
下面先容compiler包的利用要领。
先容
在R中可以利用enableJIT()这个要领,或在R启动时把情况变量R_ENABLE_JIT设为一个非负数的值来激活JIT。enableJIT要领的参数与此非负值的意义如下:
假如base等包未被编译过,那么首次激活JIT时会稍有迟顿。
示例
以下例子是对“?compile”的例子修改而来的。首先我们界说两个函数。
##### Functions #####
is.compile <-
function
(func)
{
# 这个函数可以让我们知道它是否已经被编译为字节码
#If you have a better idea for how to do this – please let me know…
if
(
class
(func) !=
“function”
)
stop
(
“You need to enter a function”
)
last_2_lines <-
tail
(
capture.output
(func),2)
any
(
grepl
(
“bytecode:”
, last_2_lines))
# returns TRUE if it finds the text “bytecode:” in any of the last two lines of the function’s print
}
# lapply的老版本
slow_func <-
function
(X, FUN, …) {
FUN <-
match.fun
(FUN)
if
(!
is.list
(X))
X <-
as.list
(X)
rval <-
vector
(
“list”
,
length
(X))
for
(i
in
seq
(along = X))
rval[i] <-
list
(
FUN
(X[[i]], …))
names
(rval) <-
names
(X)
# keep `names’ !
return
(rval)
}
从这个功效我们可以看到,编译后的函数给我们带来了3倍阁下的机能晋升。# 编译后的版本
require
(compiler)
slow_func_compiled <-
cmpfun
(slow_func)
请留意最后一行,我们是如何手工把这个函数编译为字节码的。
然后,让我们来运行一下这这两个函数(编译的与未编译的)许多次,并记下它们运行所需的时间。
fo <-
function
()
for
(i
in
1:1000)
slow_func
(1:100, is.null)
fo_c <-
function
()
for
(i
in
1:1000)
slow_func_compiled
(1:100, is.null)
system.time
(
fo
())
system.time
(
fo_c
())
# > system.time(fo())
# user system elapsed
# 0.54 0.00 0.57
# > system.time(fo_c())
# user system elapsed
# 0.17 0.00 0.17
那么,假如我们把cmpfun函数应用到fo上呢?
fo_compiled <-
cmpfun
(fo)
system.time
(
fo_compiled
())
# doing this, will not change the speed at all:
# user system elapsed
# 0.58 0.00 0.58
我们看到cmpfun并没有给我们带来任何机能晋升。为什么会这样呢?这是因为slow_func未被编译。
|
cmpfun()这个函数只会编译它所包括的函数,而对付更深条理的函数,它就无能为力了。这时enableJIT()函数就可以助一臂之力。
|
我们发明fo函数溘然变快了。这是因为enableJIT()这个函数把每个函数都转换成了字节码。这时假如我们再查抄一下:
|
这就意味着,假如你想只管淘汰对代码的窜改,并得到机能的晋升,你可以通过在执行所有代码前这样做
|
顺便提一下,我们可以用”enableJIT(0)”来封锁JIT,但此时已编译过的函数将仍然保持已编译的状态,除非你对它们从头举办界说(运行界说函数的代码)。