Schnellere Alternative zu deparse ()

9

Ich pflege ein Paket, das auf wiederholten Anrufen beruht deparse(control = c("keepNA", "keepInteger")). controlist immer gleich und der Ausdruck variiert. deparse()scheint viel Zeit damit zu verbringen, die gleichen Optionen wiederholt mit zu interpretieren .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

Auf einigen Systemen nehmen redundante .deparseOpts()Aufrufe tatsächlich den größten Teil der Laufzeit von deparse()( Flammengraph hier ) ein.

Ich würde wirklich gerne nur .deparseOpts()einmal aufrufen und dann den numerischen Code angeben deparse(), aber das scheint unmöglich, ohne .Internal()den C-Code direkt aufzurufen oder aufzurufen, was aus Sicht der Paketentwicklung nicht optimal ist.

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>

Gibt es eine bequeme Problemumgehung?

Landauer
quelle

Antworten:

4

1) Definieren Sie eine Funktion, die eine Kopie von deparse generiert, deren Umgebung zurückgesetzt wurde, um eine geänderte Version von .deparseOpts zu finden, die so eingestellt wurde, dass sie der Identitätsfunktion entspricht. In führen Runwir dann diese Funktion aus, um eine zu erstellen deparse2und diese auszuführen. Dies vermeidet das .Internaldirekte Ausführen .

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

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

# test
Run()

2) Eine andere Möglichkeit, dies zu tun, besteht darin, eine Konstruktorfunktion zu definieren, die eine Umgebung erstellt, in der eine modifizierte Kopie von deparsedieser Kopie abgelegt und dieser .deparseOptsals Identitätsfunktion neu definierten Kopie eine Ablaufverfolgung hinzugefügt werden kann. Geben Sie dann diese Umgebung zurück. Wir haben dann eine Funktion, die sie verwendet, und für dieses Beispiel erstellen wir eine Funktion Run, um sie zu demonstrieren und dann einfach auszuführen Run. Dies vermeidet die Verwendung.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) Ein dritte Ansatz ist , neu zu definieren , deparseindem ein neues Argument , das Satz .deparseOptseine Standard zu haben , identityund setzt die controleinen Standardwert von 65 zu haben.

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
quelle
Wow, das ist so schlau !!! Daran hätte ich nie gedacht! Ich freue mich sehr darauf, es auf allen meinen Systemen zu vergleichen!
Landau
Wenn ich (1) anwende und das backtickArgument vorberechnete, ist das Entparsen 6x schneller! Ich gehe damit. Vielen Dank für die Problemumgehung!
Landau
Hmm ... R CMD checkerkennt anscheinend den .Internal()Aufruf von Funktionen, die von (1) erzeugt werden. Ziemlich einfach zu umgehen, ich brauche nur make_deparse()(expr, control = 64, backtick = TRUE). Es ist albern, den Deparser jedes Mal zu rekonstruieren, wenn ich ihn benutze, aber er ist immer noch viel schneller als die Naivität, die deparse()ich zuvor benutzt habe.
Landau
Nicht für mich. Ich habe versucht, ein Paket mit nur den make_deparseund RunFunktionen in (1) zu erstellen und lief R CMD buildund R CMD check --as-cranunter "R version 3.6.1 Patched (2019-11-18 r77437)"und es hat sich nicht beschwert und ich brauchte keine Problemumgehungen. Sind Sie sicher, dass Sie nichts anderes tun oder zusätzlich, was dies verursacht?
G. Grothendieck
1
Es wurde durch Ihren Code verursacht, der ihn auf der obersten Ebene definiert : direct_deparse <- make_direct_deparse(). Der in der Antwort gezeigte Code achtete darauf, dies nicht zu tun, und definierte ihn nur innerhalb einer Funktion, dh innerhalb Run.
G. Grothendieck