Alternative plus rapide à l'analyse ()

9

Je maintiens un package qui repose sur des appels répétés à deparse(control = c("keepNA", "keepInteger")). controlest toujours la même, et l'expression varie. deparse()semble passer beaucoup de temps à interpréter à plusieurs reprises le même ensemble d'options avec .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

Sur certains systèmes, les .deparseOpts()appels redondants occupent en fait la majorité du temps d'exécution de deparse()( graphique des flammes ici ).

Je voudrais vraiment appeler .deparseOpts()une seule fois puis fournir le code numérique à deparse(), mais cela semble impossible sans appeler .Internal()ou appeler directement le code C, qui n'est ni optimal du point de vue du développement de package.

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-t-il une solution de contournement pratique?

landau
la source

Réponses:

4

1) Définissez une fonction qui génère une copie de parse dont l'environnement a été réinitialisé pour trouver une version modifiée de .deparseOpts qui a été définie pour être égale à la fonction d'identité. En Runnous courons alors cette fonction pour créer une deparse2et exécuter cela. Cela évite de s'exécuter .Internaldirectement.

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

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

# test
Run()

2) Une autre façon de procéder consiste à définir une fonction constructeur qui crée un environnement dans lequel placer une copie modifiée de deparseet ajouter une trace à cette copie redéfinie en .deparseOptstant que fonction d'identité. Retournez ensuite cet environnement. Nous avons ensuite une fonction qui l'utilise et pour cet exemple, nous créons une fonction Runpour la démontrer, puis nous l'exécutons Run. Cela évite d'avoir à utiliser.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) Une troisième approche consiste à redéfinir deparseen ajoutant un nouvel argument qui définit .deparseOptspour avoir une valeur par défaut de identityet définit controlpour avoir une valeur par défaut 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
la source
Wow, c'est tellement intelligent !!! Je n'aurais jamais pensé à ça! J'ai vraiment hâte de le comparer sur tous mes systèmes!
landau
Lorsque j'applique (1) et que je précalcule l' backtickargument, l'analyse est 6 fois plus rapide! Je vais avec ça. Merci beaucoup pour la solution!
landau
Hmm ..., apparemment, R CMD checkdétecte l' .Internal()appel dans les fonctions produites par (1). Assez facile à travailler, j'ai juste besoin make_deparse()(expr, control = 64, backtick = TRUE). Il est idiot de reconstruire le séparateur à chaque fois que je l'utilise, mais il est toujours beaucoup plus rapide que le naïf que deparse()j'utilisais auparavant.
landau
Pas pour moi. J'ai essayé de créer un package avec juste les fonctions make_deparseet Rundans (1) et j'ai couru R CMD buildet R CMD check --as-cransous "R version 3.6.1 Patched (2019-11-18 r77437)"et il ne s'est pas plaint et je n'ai eu besoin d'aucune solution de contournement. Êtes-vous sûr de ne pas faire quelque chose de différent ou de plus qui cause cela?
G. Grothendieck
1
Il a été causé par votre code définissant au niveau supérieur: direct_deparse <- make_direct_deparse(). Le code affiché dans la réponse a pris soin de ne pas le faire et ne l'a défini que dans une fonction, c'est-à-dire à l'intérieur Run.
G. Grothendieck