2013-05-05 59 views
10

我的S4類有一個多次調用的方法。我注意到執行時間比單獨調用類似函數時慢得多。所以我在類中添加了一個類型爲「function」的插槽,並使用該函數代替方法。下面的例子顯示了這樣做的兩種方式,並且它們都比對應的方法運行速度快得多。另外,該例子表明,該方法的速度較低並不是由於方法必須從類中檢索數據,因爲即使他們也這樣做,函數的速度更快。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) 

這(更好的方法查找)將是非常有趣的,最值得關注,而項目;在其他動態語言中學到了寶貴的經驗教訓,例如維基百科的dynamic dispatch頁面中提到的內聯緩存。

我不知道你是否做出很多方法調用的原因是因爲你的數據表示和方法的矢量化不完全?

+0

感謝您的有用建議。我的數據表示和方法沒有被矢量化的原因是:我正在使用多態。在我的代碼中,每個子類都有不同的method.foo,不同的人可能會編寫不同的方法。因此,與示例不同,每次調用method.foo都調用不同的方法,我不知道每個方法的內容是什麼。 – Soldalma 2013-05-06 15:06:20

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 

在我的電腦,裸通話時間約20納秒。將它封裝在一個函數中會增加大約200 ns - 這是創建函數執行環境的成本。 S3方法調度在12μs左右增加大約3μs和S4/ref類。