R:使用class属性更慢地列出元素的赋值

tow*_*owi 4 oop performance r list

我在S3和S4类的上下文中进行了一些分析,并观察了以下内容:

与相应普通列表上的相同操作相比,对S3对象元素的简单值赋值大约慢2-3倍.

从我的角度来看,S3类是一个带有附加属性的列表,元素只是一个数字.那么,哪些机制消耗额外的时间?

value <- 1 
obj_list <- list( a = 0 )
obj_s3 <- structure( obj_list, class = "myclass" )

system.time( 
  replicate( 100000, obj_list$a <- value)
) # ~180 ms

system.time( 
  replicate( 100000, obj_s3$a <- value)
) # ~420 ms
Run Code Online (Sandbox Code Playgroud)

Bro*_*ieG 8

只要向R变量添加一个类,就会使其成为受S3调度影响的对象.由于$<-行为类似于S3泛型,$<-将尝试根据您的对象的类进行调度.如果您查看C代码$<-,您可以看到:

/* From src/main/subassign.c

   $<-(x, elt, val)
*/
SEXP attribute_hidden do_subassign3(SEXP call, SEXP op, SEXP args, SEXP env)
{
  // ... code omitted  
  if(DispatchOrEval(call, op, "$<-", args, env, &ans, 0, 0))
      return(ans);
  // ... code omitted  
}
Run Code Online (Sandbox Code Playgroud)

DispatchOrEval如果参数是一个对象(即有一个类或是一个S4对象),它将只启动S3调度.请注意,即使对于没有方法的对象,S3 dispatch也会产生开销,因为仍然必须找到默认方法.如果我们看一下非原始的S3泛型,比如mean显然调度过程是显而易见的,那就更清楚了:

> mean
function (x, ...) 
UseMethod("mean")
<bytecode: 0x000000000fd151c0>
<environment: namespace:base>
Run Code Online (Sandbox Code Playgroud)

这表明当您mean在没有方法的情况下调用对象时,实际上发生了以下情况:

mean(obj) => UseMethod() => find method => mean.default(obj)
Run Code Online (Sandbox Code Playgroud)

额外调用和查找匹配方法的过程会增加您观察到的开销.这对于类似的东西来说并不明显,$<-或者sum因为所有这些东西都是通过C代码完成的DispatchOrEval.

为了显示:

> obj <- structure(1:10, class="wookkawooka")
> var <- 1:10
> 
> library(microbenchmark)
> microbenchmark(mean(obj), mean(var), mean.default(obj))
Unit: microseconds
              expr    min     lq     mean median      uq    max neval
         mean(obj) 12.069 13.166 16.46442 13.166 13.7145 95.813   100
         mean(var)  8.046  8.777  9.51974  8.778  9.1430 31.084   100
 mean.default(obj)  6.217  7.314  9.17234  7.680  8.0460 84.111   100
Run Code Online (Sandbox Code Playgroud)

注意,差异在这里没有显示,因为mean.default函数本身比原语具有更多的开销,$<-因此调度时间占总时间的比例较小.另外,请注意,对于非对象,调度仍然会发生(与基元不同),除了可以更快地决定使用默认方法.这就是为什么mean(var)mean.default(obj)速度慢但速度慢的原因mean(obj).

这是一篇关于S3/S4调度性能的博客文章,您可能对此感兴趣.