Alternativa mais rápida para separar ()

9

Eu mantenho um pacote que depende de chamadas repetidas para deparse(control = c("keepNA", "keepInteger")). controlé sempre a mesma e a expressão varia. deparse()parece gastar muito tempo interpretando repetidamente o mesmo conjunto de opções .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

Em alguns sistemas, as .deparseOpts()chamadas redundantes ocupam a maior parte do tempo de execução de deparse()( gráfico de chama aqui ).

Eu realmente gostaria de ligar apenas .deparseOpts()uma vez e depois fornecer o código numérico deparse(), mas isso parece impossível sem chamar .Internal()ou invocar o código C diretamente, nenhum dos quais é ideal do ponto de vista do desenvolvimento de pacotes.

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>

Existe uma solução conveniente?

landau
fonte

Respostas:

4

1) Defina uma função que gera uma cópia de deparse cujo ambiente foi redefinido para localizar uma versão alterada de .deparseOpts que foi configurada para ser igual à função de identidade. Em Runseguida, executamos essa função para criar deparse2e executar isso. Isso evita a execução .Internaldireta.

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

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

# test
Run()

2) Outra maneira de fazer isso é definir uma função construtora que crie um ambiente no qual colocar uma cópia modificada deparsee adicionar um rastreio à cópia redefinida .deparseOptscomo a função de identidade. Em seguida, retorne esse ambiente. Temos então alguma função que a utiliza e, para este exemplo, criamos uma função Runpara demonstrá-la e, em seguida, apenas executamos Run. Isso evita ter 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) Uma terceira abordagem é redefinir deparseadicionando um novo argumento que define .deparseOptscomo padrão identitye define controlcomo padrão 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
fonte
Uau, isso é tão inteligente !!! Eu nunca teria pensado nisso! Estou realmente ansioso para compará-lo em todos os meus sistemas!
Landau
Quando aplico (1) e pré- backtickcalculo o argumento, a separação é 6x mais rápida! Eu vou com isso. Muito obrigado pela solução alternativa!
Landau
Hmm ..., aparentemente, R CMD checkdetecta a .Internal()chamada nas funções produzidas por (1). Muito fácil de contornar, eu só preciso make_deparse()(expr, control = 64, backtick = TRUE). É tolice reconstruir o analisador toda vez que o uso, mas ainda é muito mais rápido do que o ingênuo deparse()que estava usando antes.
Landau
Não para mim. Eu tentei criar um pacote com apenas o make_deparsee Runfunções em (1) e correu R CMD builde R CMD check --as-cransob "R version 3.6.1 Patched (2019-11-18 r77437)"e não reclamar e eu não precisava de nenhuma solução alternativa. Tem certeza de que não está fazendo algo diferente ou que está causando isso?
G. Grothendieck
11
Ele foi causado por seu código definindo-o no nível superior: direct_deparse <- make_direct_deparse(). O código mostrado na resposta teve o cuidado de não fazer isso e o definiu apenas dentro de uma função, ou seja, dentro Run.
G. Grothendieck