Szybsza alternatywa dla deparse ()

9

Utrzymuję pakiet, który polega na wielokrotnych połączeniach z deparse(control = c("keepNA", "keepInteger")). controljest zawsze taki sam, a wyrażenie jest różne. deparse()wydaje się, że spędza dużo czasu, wielokrotnie interpretując ten sam zestaw opcji .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

W niektórych systemach nadmiarowe .deparseOpts()połączenia w rzeczywistości zajmują większość czasu działania deparse()( tutaj wykres płomienia ).

Naprawdę chciałbym po prostu zadzwonić .deparseOpts()raz, a następnie podać kod numeryczny deparse(), ale wydaje się to niemożliwe bez bezpośredniego wywołania .Internal()lub wywołania kodu C, z których żaden nie jest optymalny z punktu widzenia rozwoju pakietu.

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>

Czy istnieje wygodne obejście?

lando
źródło

Odpowiedzi:

4

1) Zdefiniuj funkcję, która generuje kopię deparse, której środowisko zostało zresetowane, aby znaleźć zmienioną wersję .deparseOpts, która została ustawiona jako równa funkcji tożsamości. W RunNastępnie uruchomić tę funkcję, aby stworzyć deparse2i wykonać to. Pozwala to uniknąć .Internalbezpośredniego uruchamiania .

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

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

# test
Run()

2) Innym sposobem na to jest zdefiniowanie funkcji konstruktora, która tworzy środowisko, w którym można umieścić zmodyfikowaną kopię deparsei dodać ślad do tej kopii na nowo definiujący .deparseOptsjako funkcję tożsamości. Następnie zwróć to środowisko. Następnie mamy jakąś funkcję, która z niej korzysta, i na ten przykład tworzymy funkcję, Runaby to zademonstrować, a następnie po prostu wykonać Run. Pozwala to uniknąć konieczności używania.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) Trzecim podejściem jest przedefiniowanie deparsepoprzez dodanie nowego argumentu, który ustawia .deparseOptsdomyślną wartość identityi ustawia wartośćcontrol domyślną wartość 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
źródło
Wow, to takie mądre !!! Nigdy bym o tym nie pomyślał! Naprawdę nie mogę się doczekać, aby przetestować go na wszystkich moich systemach!
landau
Po zastosowaniu (1) i wstępnym backtickobliczeniu argumentu odparowywanie jest 6 razy szybsze! Idę z tym. Dziękuję bardzo za obejście!
landau
Hmm ... najwyraźniej R CMD checkwykrywa .Internal()wywołanie funkcji wyprodukowanych przez (1). Potrzebuję dość łatwego obejścia make_deparse()(expr, control = 64, backtick = TRUE). Głupio jest zrekonstruować deparser za każdym razem, gdy go używam, ale wciąż jest znacznie szybszy niż naiwny, deparse()którego wcześniej używałem .
landau
Nie dla mnie. Próbowałem utworzyć pakiet z funkcjami make_deparsei Runw (1) i działałem R CMD buildi R CMD check --as-crandziałałem "R version 3.6.1 Patched (2019-11-18 r77437)", ale nie narzekałem i nie potrzebowałem żadnych obejść. Czy jesteś pewien, że nie robisz czegoś innego lub dodatkowo tego powoduje?
G. Grothendieck,
1
Było to spowodowane kodu definiującego je na poziomie górnej: direct_deparse <- make_direct_deparse(). Kod pokazany w odpowiedzi uważał, aby tego nie robić i definiował go tylko w funkcji, tj Run. W.
G. Grothendieck,