Является ли отправка метода S4 медленной?

В моем классе 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(object@x))
      }
    )

    setGeneric(
      name = "create.foo2",
      def = function(object) {standardGeneric("create.foo2")}
    )
    setMethod(
      f = "create.foo2", 
      signature = "SpeedTest",
      definition = function(object) {
        z <- object@x
        object@foo2 <- 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 <- st@foo1; x <- st@x; for (i in seq(iters)) foo1(x)}) 
       user  system elapsed 
      1.47    0.00    1.46 

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

    > # same speed as foo1 although no explicit argument
    > system.time({foo2 <- st@foo2; 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

person Soldalma    schedule 05.05.2013    source источник


Ответы (2)


Стоимость заключается в поиске метода, который начинается с нуля в каждой итерации вашего тайминга. Это можно сократить, выяснив диспетчеризацию метода один раз.

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

Это (лучше поиск метода) было бы очень интересным и стоящим проектом; есть ценные уроки, извлеченные из других динамических языков, например встроенное кэширование, упомянутое на странице динамической отправки Википедии.

Интересно, причина того, что вы делаете много вызовов методов, связана с неполной векторизацией вашего представления данных и методов?

person Martin Morgan    schedule 05.05.2013
comment
Спасибо за полезное предложение. Причина, по которой мое представление данных и методы не векторизованы: я использую полиморфизм. В моем коде у меня есть разные методы method.foo для каждого подкласса, и разные люди могут писать разные методы. Итак, в отличие от примера, каждый вызов method.foo вызывает другой метод, и я не знаю, что находится в теле каждого метода. - person Soldalma; 06.05.2013

Это не поможет вам напрямую решить вашу проблему, но гораздо проще протестировать такие вещи с помощью пакета 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

На моем компьютере голый вызов занимает около 20 нс. Включение его в функцию добавляет около 200 дополнительных нс — это стоимость создания среды, в которой происходит выполнение функции. Диспетчеризация методов S3 добавляет около 3 мкс, а классы S4/ref — около 12 мкс.

person hadley    schedule 06.05.2013