2013-05-05 6 views
10

私のS4クラスには何度も呼び出されるメソッドがあります。私は、同じような関数が独立して呼び出された場合よりも実行時間がはるかに遅いことに気付きました。だから、タイプ "function"のスロットをクラスに追加し、メソッドの代わりにその関数を使用しました。以下の例は、これを行う2つの方法を示しており、どちらも対応するメソッドよりもはるかに高速に動作します。また、この例では、メソッドの速度が遅いのは、メソッドがクラスからデータを取得する必要がないためであることが示唆されています。S4メソッドのディスパッチは遅いですか?

もちろん、このようなことは理想的ではありません。メソッドディスパッチを高速化する方法があるのだろうかと思います。助言がありますか?

setClass(Class = "SpeedTest", 
     representation = representation(
     x = "numeric", 
     foo1 = "function", 
     foo2 = "function" 
    ) 
    ) 

    speedTest <- function(n) { 
     new("SpeedTest", 
     x = rnorm(n), 
     foo1 = function(z) sqrt(abs(z)), 
     foo2 = function() {} 
    ) 
    } 

    setGeneric(
     name = "method.foo", 
     def = function(object) {standardGeneric("method.foo")} 
    ) 
    setMethod(
     f = "method.foo", 
     signature = "SpeedTest", 
     definition = function(object) { 
     sqrt(abs([email protected])) 
     } 
    ) 

    setGeneric(
     name = "create.foo2", 
     def = function(object) {standardGeneric("create.foo2")} 
    ) 
    setMethod(
     f = "create.foo2", 
     signature = "SpeedTest", 
     definition = function(object) { 
     z <- [email protected] 
     [email protected] <- function() sqrt(abs(z)) 

     object 
     } 
    ) 

    > st <- speedTest(1000) 
    > st <- create.foo2(st) 
    > 
    > iters <- 100000 
    > 
    > system.time(for (i in seq(iters)) method.foo(st)) # slowest by far 
     user system elapsed 
     3.26 0.00 3.27 

    > # much faster 
    > system.time({foo1 <- [email protected]; x <- [email protected]; for (i in seq(iters)) foo1(x)}) 
     user system elapsed 
     1.47 0.00 1.46 

    > # retrieving [email protected] instead of x does not affect speed 
    > system.time({foo1 <- [email protected]; for (i in seq(iters)) foo1([email protected])}) 
     user system elapsed 
     1.47 0.00 1.49 

    > # same speed as foo1 although no explicit argument 
    > system.time({foo2 <- [email protected]; for (i in seq(iters)) foo2()}) 
     user system elapsed 
     1.44 0.00 1.45 

    # Cannot increase speed by using a lambda to "eliminate" the argument of method.foo 
    > system.time({foo <- function() method.foo(st); for (i in seq(iters)) foo()}) 
     user system elapsed 
     3.28 0.00 3.29 

答えて

14

コストは方法のルックアップで、タイミングの繰り返しごとにゼロから開始します。これは、この(より良い方法ルックアップ)は非常に興味深いと価値、しばらくプロジェクトだろう

METHOD <- selectMethod(method.foo, class(st)) 
for (i in seq(iters)) METHOD(st) 

一度メソッドディスパッチを考え出すことにより、短絡することができます。 Wikipediaのdynamic dispatchページに記載されているインラインキャッシングなど、他の動的言語で学んだ貴重な教訓があります。

多くのメソッド呼び出しを行っている理由は、データ表現やメソッドが完全にベクトル化されていないためですか?

+0

有用な提案をありがとう。私のデータ表現とメソッドがベクトル化されない理由:多形性を使用しています。私のコードでは、私はサブクラスごとに異なるメソッドを持っています。異なる人が異なるメソッドを書くかもしれません。したがって、例とは異なり、method.fooを呼び出すたびに別のメソッドが呼び出され、各メソッドの本体に何が含まれているのかわかりません。 – Soldalma

6

これはあなたの問題を直接お手伝いをしませんが、それはマイクロベンチマークパッケージで原料のこの種のベンチマークの方がはるかに簡単です:私のコンピュータで

f <- function(x) NULL 

s3 <- function(x) UseMethod("s3") 
s3.integer <- function(x) NULL 

A <- setClass("A", representation(a = "list")) 
setGeneric("s4", function(x) standardGeneric("s4")) 
setMethod(s4, "A", function(x) NULL) 

B <- setRefClass("B") 
B$methods(r5 = function(x) NULL) 

a <- A() 
b <- B$new() 

library(microbenchmark) 
options(digits = 3) 
microbenchmark(
    bare = NULL, 
    fun = f(), 
    s3 = s3(1L), 
    s4 = s4(a), 
    r5 = b$r5() 
) 
# Unit: nanoseconds 
# expr min lq median uq max neval 
# bare 13 20  22 29 36 100 
# fun 171 236 270 310 805 100 
# s3 2025 2478 2651 2869 8603 100 
# s4 10017 11029 11528 11905 36149 100 
# r5 9080 10003 10390 10804 61864 100 

、裸の呼び出しは20ns程度かかります。それを関数にラップすると余分に200 nsが追加されます。これは関数の実行が発生する環境を作成するコストです。 S3メソッドディスパッチは約3μsとS4/refクラスを約12μs追加します。

関連する問題