11

我有一个 R 包,它当前使用S3类系统,有两个不同的类和几种用于通用 S3 函数的方法,如plot,logLikupdate(用于模型公式更新)。if/else由于没有基于 in 中的两个参数的继承或分派,我的代码在所有有效性检查和结构中变得更加复杂S3,我开始考虑将我的包转换为S4. S3但后来我开始阅读vs的优缺点S4,我不再那么确定了。我发现了关于 S3 与 S4 效率问题的R-bloggers 博客文章,就像 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实际上要快一点。然后我阅读了关于使用vs的SO 问题,这非常有利于. 特别是@joshua-ulrich 的回答让我怀疑,因为它说S3S4S3S4

任何插槽更改都需要完整的对象副本

如果考虑到我在优化模型的对数似然时在每次迭代中更新对象的情况,那感觉就像一个大问题。经过一番谷歌搜索后,我发现John Chambers 发布了关于这个问题的帖子,这似乎在 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显然更慢。

4

3 回答 3

8
  • 首先,您可以轻松地为 S4 类提供 S3 方法:

    > 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
    

编辑:由于哈德利的评论,将实验更改为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 方法一样快。但是,没有调度的调用仍然更快。

  • 有一些东西比 S3 工作得更好,例如as.matrixas.data.frame
    出于某种原因,将这些定义为 S3 意味着例如lm (formula, objS4)可以开箱即用。这不适用于as.data.frame被定义为 S4 方法。

  • debug调用S3 方法也更方便。

  • 其他一些东西不适用于 S3,例如在第二个参数上调度。

  • 性能是否会明显下降显然取决于你的类,即你有什么样的结构、对象有多大以及方法被调用的频率。几微秒的方法调度与毫秒甚至秒的计算无关。但是当一个函数被调用数十亿次时,μs 确实很重要。

  • 导致某些经常调用的函数的性能显着下降的一件事[是 S4 验证(validObject在跳过此步骤。

  • 如果您有大量数据并且引用调用会帮助您提高性能,您可能需要查看引用类。到目前为止,我从未真正与他们合作过,所以我无法对此发表评论。

于 2013-03-23T20:47:48.577 回答
2

If you are concerned about performance, benchmark it. If you really need multiple inheritance or multiple dispatch, use S4. Otherwise use S3.

于 2013-03-23T21:44:40.030 回答
1

(这非常接近“可能引发意见的问题”的边界,但相信这是一个重要的问题,您已经为此提供了代码和数据以及有用的引用,所以我希望没有投票结束。)

我承认我从来没有真正理解过 S4 编程模型。然而,Chambers 的帖子说的是@<-,即插槽分配,被重新实现为原语而不是作为闭包,因此当一个组件被更改时,它不需要对象的完整副本。因此,早期的情况将在 R 3.0.0 beta 中改变。在我的机器上(一台运行 R 3.0.0 beta 的 5 岁 MacPro),相对差异更大。但是,我认为这不一定是一个好的测试,因为它不会更改具有多个插槽的命名对象的现有副本。

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,因为你的大脑结构比我的更灵活,而且有很多非常聪明的人,除了 John Chambers 之外,还有很多非常聪明的人,Douglas Bates 和 Martin Maechler,他们使用 S4 方法处理需要大量处理的包. Matrix 和 lme4 包都对关键功能使用 S4 方法。

于 2013-03-23T19:37:52.773 回答