2013-05-05 12 views
10

Moja klasa S4 ma metodę, która jest wywoływana wiele razy. Zauważyłem, że czas wykonania jest znacznie wolniejszy niż byłoby, gdyby podobna funkcja została wywołana niezależnie. Dodałem więc slot z typem "function" do mojej klasy i użyłem tej funkcji zamiast metody. Poniższy przykład pokazuje dwa sposoby zrobienia tego, a obie z nich działają znacznie szybciej niż odpowiednia metoda. Również przykład sugeruje, że niższa prędkość metody nie jest spowodowana metodą pobierania danych z klasy, ponieważ funkcje są szybsze nawet wtedy, gdy również to robią.Czy rozsyłanie metodą S4 jest wolne?

Oczywiście ten sposób robienia rzeczy nie jest idealny. Zastanawiam się, czy istnieje sposób na przyspieszenie wysyłki metody. Jakieś sugestie?

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 

Odpowiedz

14

Koszt obejmuje wyszukiwanie metod, które rozpoczynają się od zera w każdej iteracji czasu. To mogą być zwierane przez zastanawianie się metodę wysyłkę raz

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

ten (lepsza metoda look-up) byłby to projekt bardzo ciekawy i warto-podczas; istnieją cenne doświadczenia zdobyte w innych językach dynamicznych, np. wbudowane buforowanie wspomniane na stronie Wikipedii: dynamic dispatch.

Zastanawiam się, czy powodem, dla którego wykonujesz wiele wywołań metodowych, jest niepełna wektoryzacja reprezentacji danych i metod?

+0

Dzięki za przydatną sugestię. Powód, dla którego moja reprezentacja danych i metody nie są wektoryzowane: używam polimorfizmu. W moim kodzie mam inną metodę.foo na podklasę, a różni ludzie mogą pisać różne metody. Tak więc, inaczej niż w przykładzie, każde wywołanie metody.foo wywołuje inną metodę i nie wiem, co jest w treści każdej z metod. – Soldalma

6

nie pomóc bezpośrednio ze swojego problemu, ale jest to o wiele łatwiejsze do odniesienia tego rodzaju rzeczy z pakietu microbenchmark:

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 

Na moim komputerze, gołe rozmowa trwa około 20 ns. Zawijanie go w funkcję dodaje około 200 ns - jest to koszt stworzenia środowiska, w którym odbywa się wykonywanie funkcji. Wysłanie metody S3 dodaje około 3 μs i klasy S4/ref około 12 μs.

Powiązane problemy