我有一个 R 包,它目前使用 S3
类系统,具有两个不同的类和几种用于通用 S3 函数的方法,如 plot
、 logLik
和 update
(用于模型公式更新)。由于没有基于 if/else
中的两个参数的继承或分派(dispatch),我的代码在所有有效性检查和 S3
结构中变得更加复杂,我开始考虑将我的包转换为 S4
。但后来我开始阅读 S3
与 S4
的优缺点,我不再那么确定了。我发现 R-bloggers blog post 关于 S3 与 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 question 关于使用 S3
与 S4
的内容,这非常有利于 S3
。特别是@joshua-ulrich 的回答让我怀疑 S4
,因为它说any slot change requires a full object copy
如果我考虑到我在优化模型的对数似然时在每次迭代中更新我的对象的情况,那感觉就像一个大问题。经过一番谷歌搜索,我发现了关于这个问题的 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){
obj@y<-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
基准测试是在 64 位 Windows 7 上的 R 2.15.2 上执行的。所以这里的
S4
显然更慢。
最佳答案
> extract <- function (x, ...) x@x
> 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
编辑:由于 Hadley 的评论,将实验更改为
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
plot
的 S4 方法我得到: plot(objS4) 19835 20428.5 21336.5 22175.0 58876 100
所以是的,
[
有一个异常快速的调度机制(这很好,因为我认为提取和相应的替换函数是最常调用的方法之一。但是不,S4 调度并不比 S3 调度慢。这里 S4 对象上的 S3 方法与 S3 对象上的 S3 方法一样快。但是,没有调度的调用仍然更快。
as.matrix
或 as.data.frame
出于某种原因,将这些定义为 S3 意味着例如lm (formula, objS4)
开箱即用。这不适用于 as.data.frame
被定义为 S4 方法。 debug
更方便。在 S3 方法上。 [
)性能显着下降的一件事是 S4 验证(在 validObject
中完成了相当多的检查) - 但是,我很高兴拥有它,所以我使用它。在内部,我使用跳过此步骤的主力函数。 关于performance - 将使用 S3 的包转换为 S4 类,性能会下降吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15590656/