Более быстрая альтернатива 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>

Есть ли удобный обходной путь?

ландо
источник

Ответы:

4

1) Определите функцию, которая генерирует копию файла deparse, среда которого была сброшена, чтобы найти измененную версию .deparseOpts, которая была установлена ​​равной функции идентификации. В 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()я использовал раньше.
Ландау
Не для меня. Я попытался создать пакет с только make_deparseи Runфункции в (1) и бегала , R CMD buildи R CMD check --as-cranпод "R version 3.6.1 Patched (2019-11-18 r77437)"и не жаловался , и мне не нужно никаких обходных путей. Вы уверены, что не делаете что-то другое или кроме того, что вызывает это?
Г. Гротендик
1
Это было вызвано вашим кодом , определяющим его на уровне верхней части: direct_deparse <- make_direct_deparse(). Код, показанный в ответе, старался не делать этого и определял его только внутри функции, то есть внутри Run.
Г. Гротендик