Alternatif lebih cepat daripada deparse ()

9

Saya mengelola paket yang bergantung pada panggilan berulang deparse(control = c("keepNA", "keepInteger")). controlselalu sama, dan ekspresinya bervariasi. deparse()tampaknya menghabiskan banyak waktu berulang kali menafsirkan set pilihan yang sama dengan .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

Pada beberapa sistem, .deparseOpts()panggilan berlebihan sebenarnya mengambil sebagian besar runtime deparse()( grafik nyala di sini ).

Saya benar-benar ingin hanya memanggil .deparseOpts()sekali dan kemudian menyediakan kode numerik deparse(), tetapi itu tampak mustahil tanpa memanggil .Internal()atau memanggil kode C secara langsung, yang keduanya tidak optimal dari perspektif pengembangan paket.

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>

Apakah ada solusi yang nyaman?

Landau
sumber

Jawaban:

4

1) Tentukan fungsi yang menghasilkan salinan deparse yang lingkungannya telah disetel ulang untuk menemukan versi .deparseOpts yang diubah yang telah disetel sama dengan fungsi identitas. Di Runkemudian kita jalankan fungsi itu untuk membuat deparse2dan menjalankannya. Ini menghindari berjalan .Internallangsung.

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

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

# test
Run()

2) Cara lain untuk melakukan ini adalah dengan mendefinisikan fungsi konstruktor yang menciptakan lingkungan untuk meletakkan salinan yang dimodifikasi deparsedan menambahkan jejak ke salinan yang mendefinisikan ulang .deparseOptssebagai fungsi identitas. Kemudian kembalikan lingkungan itu. Kami kemudian memiliki beberapa fungsi yang menggunakannya dan untuk contoh ini kami membuat fungsi Rununtuk mendemonstrasikannya dan kemudian jalankan Run. Ini menghindari harus digunakan.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) Pendekatan ketiga adalah mendefinisikan ulang deparsedengan menambahkan argumen baru yang menetapkan .deparseOptsuntuk memiliki default identitydan menetapkan controluntuk memiliki default 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
sumber
Wow, itu sangat pintar !!! Saya tidak akan pernah memikirkan itu! Sangat menantikan untuk melakukan benchmark pada semua sistem saya!
Landau
Ketika saya menerapkan (1) dan precompute backtickargumen, deparsing adalah 6x lebih cepat! Saya akan melakukannya. Terima kasih banyak atas solusinya!
Landau
Hmm ..., ternyata, R CMD checkmendeteksi .Internal()panggilan dalam fungsi yang dihasilkan oleh (1). Cukup mudah untuk dikerjakan, saya hanya perlu make_deparse()(expr, control = 64, backtick = TRUE). Adalah konyol untuk merekonstruksi deparser setiap kali saya menggunakannya, tetapi masih jauh lebih cepat daripada yang naif yang deparse()saya gunakan sebelumnya.
Landau
Bukan untuk saya. Saya mencoba membuat sebuah paket dengan hanya fungsi make_deparsedan Rundi (1) dan berlari R CMD builddan di R CMD check --as-cranbawah "R version 3.6.1 Patched (2019-11-18 r77437)"dan itu tidak mengeluh dan saya tidak memerlukan solusi. Apakah Anda yakin tidak melakukan sesuatu yang berbeda atau yang menyebabkan hal ini?
G. Grothendieck
1
Hal ini disebabkan oleh kode Anda mendefinisikan itu di tingkat atas: direct_deparse <- make_direct_deparse(). Kode yang ditunjukkan dalam jawaban berhati-hati untuk tidak melakukan itu dan hanya mendefinisikannya dalam suatu fungsi, yaitu di dalamRun .
G. Grothendieck