Alternativa más rápida a deparse ()

9

Mantengo un paquete que se basa en llamadas repetidas a deparse(control = c("keepNA", "keepInteger")). controles siempre igual, y la expresión varía. deparse()parece pasar mucho tiempo interpretando repetidamente el mismo conjunto de opciones con .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

En algunos sistemas, las .deparseOpts()llamadas redundantes en realidad ocupan la mayor parte del tiempo de ejecución de deparse()( gráfico de llama aquí ).

Realmente me gustaría llamar .deparseOpts()una vez y luego proporcionar el código numérico deparse(), pero eso parece imposible sin llamar .Internal()o invocar el código C directamente, ninguno de los cuales es óptimo desde la perspectiva del desarrollo del paquete.

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>

¿Hay una solución conveniente?

landó
fuente

Respuestas:

4

1) Defina una función que genere una copia de deparse cuyo entorno se haya restablecido para encontrar una versión alterada de .deparseOpts que se haya configurado para que sea igual a la función de identidad. En Runque ejecute esta función para crear una deparse2y ejecutar eso. Esto evita correr .Internaldirectamente.

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

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

# test
Run()

2) Otra forma de hacerlo es definir una función de constructor que cree un entorno en el que colocar una copia modificada deparsey agregar un rastro a esa copia redefiniendo .deparseOptscomo la función de identidad. Luego devuelve ese entorno. Luego tenemos alguna función que la usa y para este ejemplo creamos una función Runpara demostrarlo y luego simplemente ejecutar Run. Esto evita tener que usar.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) Un tercer enfoque es redefinir deparseagregando un nuevo argumento que establece .deparseOptstener un valor predeterminado de 65 identityy establece controlque tenga un valor predeterminado de 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()
G. Grothendieck
fuente
Wow, eso es tan inteligente! ¡Nunca habria pensado en eso! ¡Tengo muchas ganas de compararlo en todos mis sistemas!
landau
Cuando aplico (1) y precalculo el backtickargumento, el análisis es 6 veces más rápido. Me voy con eso. Muchas gracias por la solución!
landau
Hmm ..., aparentemente, R CMD checkdetecta la .Internal()llamada en funciones producidas por (1). Bastante fácil de solucionar, solo necesito make_deparse()(expr, control = 64, backtick = TRUE). Es una tontería reconstruir el analizador cada vez que lo uso, pero aún es mucho más rápido que el ingenuo deparse()que estaba usando antes.
landau
No para mí. Intenté crear un paquete con solo las funciones make_deparsey Runen (1) y ejecuté R CMD buildy R CMD check --as-crandebajo "R version 3.6.1 Patched (2019-11-18 r77437)"y no se quejó y no necesité ninguna solución. ¿Estás seguro de que no estás haciendo algo diferente o que además está causando esto?
G. Grothendieck
1
Fue causado por el código de definirla en el nivel superior: direct_deparse <- make_direct_deparse(). El código que se muestra en la respuesta tuvo cuidado de no hacer eso y solo lo definió dentro de una función, es decir, dentro Run.
G. Grothendieck