使用S3转换为S4类,是否会出现性能下降?

Jou*_*ske 11 oop performance r s4

我有一个R包当前使用S3类系统,有两个不同的类和几个通用S3函数的方法,如plot(logLikupdate模型公式更新).由于我的代码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
Run Code Online (Sandbox Code Playgroud)

所以在这个简单的例子中,S4实际上有点快.然后我读了关于使用vs的问题,这非常有利于.特别是@ joshua-ulrich的回答让我怀疑,正如它所说的那样S3S4S3S4

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

如果我考虑我的情况,在优化模型的对数可能性时,我在每次迭代中更新我的对象,这感觉就像一个大问题.经过一些谷歌搜索后,我发现John Chambers发布了这个问题,这个问题似乎在R 3.0.0中有所改变.

因此,尽管我认为S4在我的代码中使用类更清晰(例如,从主模型类继承的更多类)以及有效性检查等是有益的,但我现在想知道它是否值得所有工作在性能?所以,明智的性能,有没有真正之间的性能差异S3S4?我应该考虑其他一些性能问题吗?或者甚至可以对这个问题进行一般性的说明?

编辑:正如@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
Run Code Online (Sandbox Code Playgroud)

基准测试在64位Windows 7上的R 2.15.2上执行.所以这里S4显然比较慢.

cbe*_*ica 6

  • 首先,您可以轻松地为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
    
    Run Code Online (Sandbox Code Playgroud)

编辑:由于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
Run Code Online (Sandbox Code Playgroud)

对于plot我获得的S4方法:

    plot(objS4) 19835 20428.5 21336.5 22175.0 58876   100
Run Code Online (Sandbox Code Playgroud)

所以是的,[有一个特别快的调度机制(这很好,因为我认为提取和相应的替换函数是最常被调用的方法之一.但不,S4调度不比S3调度慢.


这里S4对象上的S3方法与S3对象上的S3方法一样快.但是,没有派遣的呼叫仍然更快.

  • 有些东西比S3更好用,as.matrix或者as.data.frame
    由于某些原因,将它们定义为S3意味着例如lm (formula, objS4)开箱即用.这不适用于as.data.frame定义为S4方法.

  • 调用debugS3方法也更方便.

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

  • 是否会出现明显的性能下降显然取决于您的类,即您拥有的结构类型,对象的大小以及调用方法的频率.通过计算ms或甚至s,几μs的方法调度无关紧要.但是,当一个函数被调用数十亿次时,μs就很重要了.

  • 对于一些名为often([)的函数,导致显着性能下降的一件事是S4验证(完成了相当数量的检查validObject) - 然而,我很高兴拥有它,所以我使用它.在内部我使用了主力函数跳过此步骤.

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