【问题标题】:Possible shortcomings for using JIT with R?将 JIT 与 R 结合使用的可能缺点?
【发布时间】:2018-03-06 22:20:20
【问题描述】:

我最近发现可以使用编译器包在 R 中使用 JIT(即时)编译(我在 a recent blog post 中总结了我对这个主题的发现)。

我被问到的一个问题是:

有什么陷阱吗?听起来好得令人难以置信,只写一行 代码,就是这样。

环顾四周后,我发现一个可能与 JIT 的“启动”时间有关的问题。但是在使用 JIT 时还有什么需要注意的问题吗?

我想R的环境架构会有一些限制,但我想不出一个简单的例子来说明这个问题,任何建议或危险信号会有很大帮助吗?

【问题讨论】:

  • 我不确定性能是否会受到影响(除了初始编译(可能还会增加内存使用量)),但“注意:没有可见的绑定”消息对于新手来说通常是压倒性的(例如,如果使用 ggplot2) 并且可以摆脱制表符完成(至少,它们适合我)
  • 你好 mweylandt。你知道那个错误消息是什么意思吗?
  • 当我创建新版本时,我一直将ByteCompile: true 放在我的包的说明文件中,它似乎工作正常。我做了一个小测试http://www.johnmyleswhite.com/notebook/2012/03/31/julia-i-love-you/comment-page-1/#comment-19522 和字节编译版本fib2c 的运行速度比普通测试fib2a 快4 倍。在某些情况下,即使没有字节编译,R 也已经很快了(例如,在下面使用 C 的高度矢量化代码),在这些情况下,显然几乎没有加速的机会——它主要用于慢速 R 代码。
  • 另一种方法是在加载函数时只编译函数,例如您的私人图书馆等。我在这里注意到了类似的目的:stackoverflow.com/questions/9815378/…
  • 嗨,Dirk,我知道(甚至在帖子中提到过)。这是否意味着使用 JIT 在任何情况下都没有缺点?!

标签: performance r compiler-construction jit


【解决方案1】:

使用 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

有什么解释吗?

【讨论】:

  • 这很奇怪,所以在 fo 中编译循环会导致问题。如果你正常编译它就不会发生。 ideone.com/Nu8IZ ,注意 rpart 已经被字节编译。
  • 编译需要半分钟:我看到相同的(2.8 s - 42.6 s),但再次执行 system.time (fo ()) 只需 2.6 s。
  • rpart,我相信,调用 C/Fortran。这是对 R 的 JIT 能力的一个很好的测试吗?创建一个完全依赖 R 代码的函数不是更好吗?
【解决方案2】:

上面给出的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

【讨论】:

    【解决方案3】:

    原则上,一旦字节码被编译和加载,它的解释速度应该至少与原始 AST 解释器一样快。一些代码将受益于大幅加速,这通常是具有大量标量操作和循环的代码,其中大部分时间都花在了 R 解释上(我已经看到了 10 倍加速的示例,但任意微基准确实可以根据需要夸大这一点)。一些代码将以相同的速度运行,这通常是代码很好地矢量化,因此几乎没有时间在解释上。现在,编译本身可能很慢。因此,当即时编译器猜测它不会得到回报时,它现在不会编译函数(并且启发式随着时间的推移而变化,这已经在 3.4.x 中了)。启发式方法并不总能猜对,因此可能会出现编译无法获得回报的情况。典型的有问题的模式是代码生成、代码修改和对闭包中捕获的环境绑定的操作。

    包可以在安装时进行字节编译,这样编译成本就不会在运行时(重复地)支付,至少对于提前知道的代码是这样。这现在是 R 开发版本中的默认设置。虽然加载已编译代码比编译它要快得多,但在某些情况下,甚至可能加载不会执行的代码,因此实际上可能会有开销,但总体而言预编译是有益的。最近对 GC 的一些参数进行了调整,以降低加载不会执行的代码的成本。

    我对包编写者的建议是使用默认值(即时编译现在在发布版本中默认启用,在包安装时字节编译现在在开发版本中启用)。如果您发现字节码编译器性能不佳的示例,请提交错误报告(我在早期版本中也看到过涉及rpart 的案例)。我建议不要使用代码生成和代码操作,尤其是在热循环中。这包括在闭包捕获的环境中定义闭包、删除和插入绑定。绝对不应该在热循环中使用eval(parse(text=(如果没有字节编译,这已经很糟糕了)。使用分支总是比动态生成新的闭包(没有分支)更好。此外,最好用循环编写代码,而不是用巨大的表达式(没有循环)动态生成代码。现在使用字节码编译器,现在通常可以在 R 中编写在标量上运行的循环(性能不会像以前那么差,所以人们可以更频繁地摆脱对性能关键部分切换到 C 的情况) .

    【讨论】:

      【解决方案4】:

      在上一个答案的基础上,实验表明问题不是与循环的编译有关,而是与闭包的编译有关。 [enableJIT(0) 或 enableJIT(1) 使代码保持快速,enableJIT(2) 显着减慢它,并且 enableJIT(3) 比前一个选项稍快(但仍然非常慢)]。同样与 Hansi 的评论相反, cmpfun 在类似程度上减慢了执行速度。

      【讨论】:

      • 很明显,导致不同结果的原因是不同版本的 R。今天使用 R 3.3.2,无论 i 是否为 0,enableJIT(i) 给出完全相同的时间(1.21 秒), 1、2 或 3。我将把解释留给其他人,但给我的信息是它现在已经在没有用户帮助的情况下进行了优化。
      猜你喜欢
      • 2020-01-26
      • 2011-10-30
      • 2017-06-09
      • 2013-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-01
      • 2015-01-07
      相关资源
      最近更新 更多