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?
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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With