Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Faster alternative to deparse()

Tags:

r

I maintain a package that relies on repeated calls to deparse(control = c("keepNA", "keepInteger")). control is always the same, and the expression varies. deparse() seems to spend a lot of time repeatedly interpreting the same set of options with .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

On some systems, redundant .deparseOpts() calls actually take up the majority of the runtime of deparse() (flame graph here).

I would really like to just call .deparseOpts() once and then supply the numeric code to deparse(), but that appears impossible without calling .Internal() or invoking the C code directly, neither of which is optimal from a package development perspective.

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>

Is there a convenient workaround?

like image 853
landau Avatar asked Dec 04 '19 00:12

landau


1 Answers

1) Define a function which generates a copy of deparse whose environment has been reset to find an altered version of .deparseOpts which has been set to be equal to the identity function. In Run we then run that function to create a deparse2 and execute that. This avoids running .Internal directly.

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

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

# test
Run()

2) Another way to do this is to define a constructor function which creates an environment in which to put a modified copy of deparse and add a trace to that copy redefining .deparseOpts as the identity function. Then return that environment. We then have some function which uses it and for this example we create a function Run to demonstrate it and then just execute Run. This avoids having to use .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) A third approach is to redefine deparse by adding a new argument which sets .deparseOpts to have a default of identity and sets the control to have a default of 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()
like image 187
G. Grothendieck Avatar answered Oct 26 '22 23:10

G. Grothendieck