利用即时编译(JIT)技能提高R的执行效率
当前位置:以往代写 > 其他教程 >利用即时编译(JIT)技能提高R的执行效率
2019-06-14

利用即时编译(JIT)技能提高R的执行效率

利用即时编译(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要领的参数与此非负值的意义如下:

  • 0 – 封锁JIT

  • 1 – 在第一次挪用前编译闭包

  • 2 – 包罗1,而且在闭包被复制时也先行编译

  • 3 – 包罗2,而且在执行for, while, repeat函数前执行编译

  • 假如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)
    }

    # 编译后的版本
    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


    从这个功效我们可以看到,编译后的函数给我们带来了3倍阁下的机能晋升。
    那么,假如我们把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未被编译。






    is.compile(slow_func)
    # [1] FALSE
    is.compile(fo)
    # [1] FALSE
    is.compile(fo_compiled)
    # [1] TRUE


    cmpfun()这个函数只会编译它所包括的函数,而对付更深条理的函数,它就无能为力了。这时enableJIT()函数就可以助一臂之力。






    enableJIT(3)
    system.time(fo())
    # user system elapsed
    # 0.19 0.00 0.18


    我们发明fo函数溘然变快了。这是因为enableJIT()这个函数把每个函数都转换成了字节码。这时假如我们再查抄一下:






    is.compile(fo)
    # [1] TRUE # when it previously was not compiled, only fo_compiled was…
    is.compile(slow_func)
    # [1] TRUE # when it previously was not compiled, only slow_func_compiled was..


    这就意味着,假如你想只管淘汰对代码的窜改,并得到机能的晋升,你可以通过在执行所有代码前这样做






    require(compiler)
    enableJIT(3)


    顺便提一下,我们可以用”enableJIT(0)”来封锁JIT,但此时已编译过的函数将仍然保持已编译的状态,除非你对它们从头举办界说(运行界说函数的代码)。

      关键字:

    在线提交作业