Преобразование пакета с использованием классов S3 в S4, будет ли падение производительности?
У меня есть пакет R, в котором в настоящее время используется система класса S3
, с двумя разными классами и несколькими методами для общих функций S3, таких как plot
, logLik
и update
(для обновления формулы модели). Поскольку мой код стал более сложным со всеми проверками достоверности и структурами if/else
из-за того, что наследования или отправки наследования не было на основе двух аргументов в S3
, я начал думать о преобразовании моего пакета в S4
, Но затем я начал читать о преимуществах и недостатках S3
по сравнению с S4
, и я уже не так уверен. Я нашел сообщение блога R-bloggers об эффективности в S3 vs S4, и, поскольку это было 5 лет назад, я тестировал то же самое сейчас:
library(microbenchmark)
setClass("MyClass", representation(x="numeric"))
microbenchmark(structure(list(x=rep(1, 10^7)), class="MyS3Class"),
new("MyClass", x=rep(1, 10^7)) )
Unit: milliseconds
expr
structure(list(x = rep(1, 10^7)), class = "MyS3Class")
new("MyClass", x = rep(1, 10^7))
min lq median uq max neval
148.75049 152.3811 155.2263 159.8090 323.5678 100
75.15198 123.4804 129.6588 131.5031 241.8913 100
Итак, в этом простом примере S4
на самом деле бит быстрее. Затем я прочитал вопрос SO об использовании S3
vs S4
, что было в значительной степени в пользу S3
. Особенно ответ @joshua-ulrich заставил меня возражать против S4
, поскольку он сказал, что
для любого изменения слота требуется полная копия объекта
Это кажется большой проблемой, если я рассмотрю мой случай, когда я обновляю свой объект на каждой итерации при оптимизации логарифмической вероятности моей модели. После некоторого поиска я нашел John Chambers post об этой проблеме, которая, кажется, меняется в R 3.0.0.
Так что, хотя я считаю, что было бы полезно использовать классы S4
для некоторой ясности в моих кодах (например, больше классов, наследующих от основного класса модели), а также для проверок действительности и т.д., теперь мне интересно, стоит ли всю работу с точки зрения производительности? Итак, производительность с точки зрения производительности, существуют ли реальные различия в производительности между S3
и S4
? Есть ли еще некоторые проблемы с производительностью, которые я должен рассмотреть? Или можно вообще что-то сказать об этой проблеме?
РЕДАКТИРОВАТЬ: Как предположили @DWin и @g-grothendieck, вышеупомянутый бенчмаркинг не учитывает случай, когда слот существующего объекта изменяется. Итак, вот еще один тест, который более уместен для истинного приложения (функции в примере могут быть функциями get/set для некоторых элементов в модели, которые изменяются при максимизации лог-правдоподобия):
objS3<-structure(list(x=rep(1, 10^3), z=matrix(0,10,10), y=matrix(0,10,10)),
class="MyS3Class")
fnS3<-function(obj,a){
obj$y<-a
obj
}
setClass("MyClass", representation(x="numeric",z="matrix",y="matrix"))
objS4<-new("MyClass", x=rep(1, 10^3),z=matrix(0,10,10),y=matrix(0,10,10))
fnS4<-function(obj,a){
[email protected]<-a
obj
}
a<-matrix(1:100,10,10)
microbenchmark(fnS3(objS3,a),fnS4(objS4,a))
Unit: microseconds
expr min lq median uq max neval
fnS3(objS3, a) 6.531 7.464 7.932 9.331 26.591 100
fnS4(objS4, a) 21.459 22.393 23.325 23.792 73.708 100
Тесты выполняются на R 2.15.2, на 64-битной Windows 7. Итак, здесь S4
явно медленнее.
Ответы
Ответ 1
-
Прежде всего, вы можете легко иметь S3-методы для классов S4:
> extract <- function (x, ...) [email protected]
> setGeneric ("extr4", def=function (x, ...){})
[1] "extr4"
> setMethod ("extr4", signature= "MyClass", definition=extract)
[1] "extr4"
> `[.MyClass` <- extract
> `[.MyS3Class` <- function (x, ...) x$x
> microbenchmark (objS3[], objS4 [], extr4 (objS4), extract (objS4))
Unit: nanoseconds
expr min lq median uq max neval
objS3[] 6775 7264.5 7578.5 8312.0 39531 100
objS4[] 5797 6705.5 7124.0 7404.0 13550 100
extr4(objS4) 20534 21512.0 22106.0 22664.5 54268 100
extract(objS4) 908 1188.0 1328.0 1467.0 11804 100
edit: из-за комментария Хэдли, измените эксперимент на plot
:
> `plot.MyClass` <- extract
> `plot.MyS3Class` <- function (x, ...) x$x
> microbenchmark (plot (objS3), plot (objS4), extr4 (objS4), extract (objS4))
Unit: nanoseconds
expr min lq median uq max neval
plot(objS3) 28915 30172.0 30591 30975.5 1887824 100
plot(objS4) 25353 26121.0 26471 26960.0 411508 100
extr4(objS4) 20395 21372.5 22001 22385.5 31359 100
extract(objS4) 979 1328.0 1398 1677.0 3982 100
для метода S4 для plot
Я получаю:
plot(objS4) 19835 20428.5 21336.5 22175.0 58876 100
Итак, [
имеет исключительно быстрый механизм отправки (что хорошо, потому что я думаю, что извлечение и соответствующие функции замены относятся к числу наиболее часто называемых методов. Но нет, отправка S4 не медленнее, чем отправка S3.
Здесь метод S3 объекта S4 выполняется так же быстро, как метод S3 на объекте S3. Однако вызов без отправки еще быстрее.
-
есть некоторые вещи, которые работают намного лучше, чем S3, такие как as.matrix
или as.data.frame
По некоторым причинам определение этих значений как S3 означает, что, например, lm (formula, objS4)
будет работать из коробки. Это не работает с as.data.frame
, определяемым как метод S4.
-
Также гораздо удобнее вызывать debug
по методу S3.
-
некоторые другие вещи не будут работать с S3, например. отправляя второй аргумент.
-
Будет ли какое-либо заметное снижение производительности, очевидно, зависит от вашего класса, то есть от того, какие структуры у вас есть, насколько велики объекты и как часто называются методы. Несколько мкс отправки метода не будут иметь значения при расчете мс или четных s. Но μs имеют значение, когда функция называется миллиардами раз.
-
Одна вещь, вызвавшая заметное снижение производительности для некоторых функций, которые часто вызываются ([
), - это проверка S4 (справедливое количество проверок, выполненных в validObject
)), однако я рад, что это, поэтому я использую его. Внутри я использую функции рабочей лошадки, которые пропускают этот шаг.
-
В случае, если у вас есть большие данные, а call-by-reference поможет вам повысить производительность, вы можете захотеть взглянуть на ссылочные классы. Я до сих пор не работал с ними, поэтому я не могу прокомментировать это.
Ответ 2
Если вас беспокоит производительность, сравните ее. Если вам действительно нужно множественное наследование или множественная отправка, используйте S4. В противном случае используйте S3.
Ответ 3
(Это довольно близко к границе "вопроса, который может вызвать мнение", но считаю, что это важный вопрос, для которого вы предложили код и данные и полезные цитаты, и поэтому я надеюсь, что нет голосов закрыть.)
Я признаю, что я никогда не понимал модель программирования S4. Однако то, что говорилось в сообщении Chambers, заключается в том, что @<-
, т.е. Назначение слотов, было повторно реализовано как примитив, а не как закрытие, так что ему не потребовалась бы полная копия объекта, когда один компонент был изменен. Таким образом, более раннее состояние дел будет изменено в R 3.0.0 beta. На моей машине (5-летняя MacPro с R.0.0.0 beta) относительная разница была еще больше. Однако я не думал, что это был хороший тест, поскольку он не изменял существующую копию именованного объекта с несколькими слотами.
res <-microbenchmark(structure(list(x=rep(1, 10^7)), class="MyS3Class"),
new("MyClass", x=rep(1, 10^7)) )
summary(res)[ ,"median"]
#[1] 145.0541 103.4064
Я думаю, вы должны пойти с S4, так как ваша структура мозга более гибкая, чем моя, и есть много очень умных людей, Дуглас Бейтс и Мартин Маэчер, чтобы назвать еще двух, кроме Джона Чамберса, которые использовали методы S4 для пакетов, которые требуют интенсивной обработки. Пакет Matrix и lme4 используют методы S4 для критических функций.