deparse()のより高速な代替手段


9

への繰り返しの呼び出しに依存するパッケージを維持していますdeparse(control = c("keepNA", "keepInteger"))control常に同じであり、表現は異なります。deparse()で同じオプションセットを繰り返し解釈するのに多くの時間を費やしているよう.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

一部のシステムでは、冗長な.deparseOpts()呼び出しが実際にdeparse()ここでのフレームグラフ)のランタイムの大部分を占めています。

私は本当に.deparseOpts()一度だけ呼び出してからdeparse()に数値コードを提供したいのですが、.Internal()Cコードを直接呼び出したり呼び出したりしないとそれは不可能に見えます。どちらもパッケージ開発の観点からは最適ではありません。

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>

便利な回避策はありますか?

回答:


4

1)識別関数に等しくなるように設定された.deparseOptsの変更されたバージョンを見つけるために環境がリセットされたdeparseのコピーを生成する関数を定義します。ではRun、私たち、その後作成するためにその機能を実行しdeparse2て、それを実行します。これにより、.Internal直接実行する必要がなくなります。

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

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

# test
Run()

2)これを行う別の方法は、変更されたコピーを入れ、deparseそのコピーにトレースを追加し.deparseOptsて、アイデンティティー関数として再定義する環境を作成するコンストラクター関数を定義することです。次に、その環境を返します。次に、それを使用する関数がいくつかあります。この例ではRun、それを示す関数を作成して実行しRunます。これにより、.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) 3番目のアプローチは、デフォルトをに設定し、にデフォルトを65にdeparse設定する新しい引数を追加して再定義することです。.deparseOptsidentitycontrol

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()

うわー、それはとても賢いです!私はそのことを考えたことはなかったでしょう!私のすべてのシステムでそれをベンチマークすることを本当に楽しみにしています!
Landau

(1)を適用してbacktick引数を事前計算すると、解析が6倍速くなります。私はそれで行きます。回避策を本当にありがとう!
Landau

うーん...明らかに、(1)によって生成された関数R CMD check.Internal()呼び出しを検出します。非常に簡単に回避できますmake_deparse()(expr, control = 64, backtick = TRUE)。デパーサーを使用するたびに再構築するのはばかげていますが、それでも、deparse()以前使用していたナイーブよりもはるかに高速です。
Landau

私のためではありません。私はちょうどでパッケージを作成しようとしたmake_deparseRun機能(1)中とRAN R CMD buildR CMD check --as-cranの下で"R version 3.6.1 Patched (2019-11-18 r77437)"、それは文句を言わなかったし、私は回避策を必要としませんでした。何か別のことをしていないのですか、それともこれを引き起こしているのですか?
G.グロタンディーク

1
あなたのコードがそれをトップレベルで定義していることが原因です:direct_deparse <- make_direct_deparse()。回答に示されているコードは、それを行わないように注意し、関数内、つまり内でのみ定義しましたRun
G. Grothendieck
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.