替代deparse()的更快方法


9

我维护一个依赖于重复调用的程序包deparse(control = c("keepNA", "keepInteger"))control始终相同,并且表达式也有所不同。deparse()似乎花了很多时间重复解释与相同的选项.deparseOpts()

microbenchmark::microbenchmark(
    a = deparse(identity, control = c("keepNA", "keepInteger")),
    b = .deparseOpts(c("keepNA", "keepInteger"))
)
# Unit: microseconds
# expr min  lq  mean median  uq  max neval
#    a 7.2 7.4 8.020    7.5 7.6 55.1   100
#    b 3.0 3.2 3.387    3.4 3.5  6.0   100

在某些系统上,冗余.deparseOpts()调用实际上占用了deparse()火焰图)的大部分运行时间。

我真的很想只调用.deparseOpts()一次,然后将数字代码提供给deparse(),但是如果不.Internal()直接调用或调用C代码,这似乎是不可能的,从软件包开发的角度来看,这两种方法都不是最佳选择。

deparse
# function (expr, width.cutoff = 60L, backtick = mode(expr) %in% 
#     c("call", "expression", "(", "function"), 
#     control = c("keepNA", "keepInteger", "niceNames", 
#         "showAttributes"), nlines = -1L) 
# .Internal(deparse(expr, width.cutoff, backtick, .deparseOpts(control), 
#     nlines))
# <bytecode: 0x0000000006ac27b8>
# <environment: namespace:base>

有一个方便的解决方法吗?

Answers:


4

1)定义一个函数,该函数生成deparse的副本,该副本的环境已重置,以查找.deparseOpts的更改版本,该版本已设置为与identity函数相等。在Run我们然后运行函数来创建deparse2并执行。这样可以避免.Internal直接运行。

make_deparse <- function() {
  .deparseOpts <- identity
  environment(deparse) <- environment()
  deparse
}

Run <- function() {
  deparse2 <- make_deparse()
  deparse2(identity, control = 65)
}

# test
Run()

2)另一种方法是定义一个构造函数,该函数创建一个环境,在该环境中放置的修改后的副本,deparse并向该副本添加跟踪以重新定义.deparseOpts为身份函数。然后返回该环境。然后,我们有了一些使用它的函数,在本示例中,我们创建一个函数Run来演示它,然后执行Run。这样避免了必须使用.Internal

make_deparse_env <- function() {
  e <- environment()
  deparse <- deparse
  suppressMessages(
    trace("deparse", quote(.deparseOpts <- identity), print = FALSE, where = e)
  )
  e
}

Run <- function() {
  e <- make_deparse_env()
  e$deparse(identity, control = 65)
}

# test
Run()

3)第三种方法是deparse通过添加一个新参数来重新定义,该参数设置.deparseOpts为默认值,identity并将设置control为默认值65。

make_deparse65 <- function() {
  deparse2 <- function (expr, width.cutoff = 60L, backtick = mode(expr) %in% 
    c("call", "expression", "(", "function"), 
    control = 65, nlines = -1L, .deparseOpts = identity) {}
  body(deparse2) <- body(deparse)
  deparse2
}

Run <- function() {
  deparse65 <- make_deparse65()
  deparse65(identity)
}

# test
Run()

哇,真是太聪明了!!!我永远不会想到这一点!真的很期待在我所有的系统上进行基准测试!
兰道

当我应用(1)并预先计算backtick参数时,解析速度快6倍!我要去。非常感谢您的解决方法!
兰道

嗯,显然,它R CMD check检测到.Internal()(1)产生的函数中的调用。我只需要很容易解决make_deparse()(expr, control = 64, backtick = TRUE)。每当我使用它时,对它进行重构都是很愚蠢的,但是它仍然比deparse()我以前使用的天真的更快。
兰道

不适合我。我尝试创建一个只包含(1)中的make_deparseRun函数的程序包,R CMD build然后R CMD check --as-cran"R version 3.6.1 Patched (2019-11-18 r77437)"其下运行,并且它没有抱怨,我不需要任何解决方法。您确定您没有在做其他事情或导致此的其他事情吗?
G. Grothendieck

1
这是由您的代码在顶级定义它引起的:direct_deparse <- make_direct_deparse()。答案中显示的代码非常小心,不要这样做,而只能在一个函数(即)内定义它Run
G. Grothendieck
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.