在C中实现S3 Generic / Methods

时间:2014-09-03 01:59:54

标签: r dispatch

我一直在寻求将我最初用R编写的一些代码实现到C中以提高性能。到目前为止,事情一直在流动,但我正在努力解决的一个问题是如何在C中实现S3 Generic / Methods,同时最大限度地减少开销。

我的函数的用例大部分时间都会直接转到默认方法,我认为我可以在泛型中实现,或者最坏的情况是在C中将一级S3调度发送到默认值。也就是说,我想允许发生非平凡的调度。

我发现了一些对DispatchOrEval的引用,但由于函数定义之前attribute_hidden中的src/eval.c声明,似乎此函数保留供内部使用:

attribute_hidden
int DispatchOrEval(SEXP call, SEXP op, const char *generic, SEXP args,
           SEXP rho, SEXP *ans, int dropmissing, int argsevald)
{

有没有其他方法可以用有限的开销直接从C实现S3调度?

作为参考,我一直在阅读以下内容:

我承认很多对我来说还是一个新手,所以到目前为止我的理解可能是完全错误的。


编辑:为了解决Martin对特定示例的请求,我正在尝试编写一个函数checkArgs(或其他类似函数),如下所示:

FUN <- function(x, y, z) {
   checkArgs(x=...,y=..., z=...)
   # More code here
}

将提供一种简单的方法来快速检查参数是否是我想要的参数。

例如:

FUN <- function(x, y, z) {
   checkArgs(x=matrix(numeric(), ncol=3),y=..., z=...)
   # More code here
}

检查Argument x是否为三列数字矩阵。我希望允许调度的函数是将xcheckArgs中的模板规范进行比较的函数(此处未显示,但很可能称为 alike 比较对象的相似性)。大多数情况下,它会使用默认的C版本,但如果用户为该泛型创建了一个方法并使用该方法的类的对象进行验证,那么将使用提供的方法。

为了使其普遍有用,包括在SPLIT-APPLY-COMBINE分析中使用,checkArgs必须非常快速地运行。因此我开始在C中重写比较函数,但是这样做,丢失了我想保留的调度功能,即使最常见的用例是依赖于默认方法。 / p>

1 个答案:

答案 0 :(得分:1)

这绝对不是完全令人满意,但解决了我的部分问题。基本上,让我们注册一个在发送之前实际检查对象性的虚假通用:

genericfun <- function(x) {
  if(is.object(x)) {
    UseMethod("genericfun")
  } else {
    #.Call(CFunNativeSymbol, x)
  }
}
genericfun.default <- function(x) {
  #.Call(CFunNativeSymbol, x)
}
obj <- structure(1:100, class="obj")
non.obj <- 1:100
microbenchmark(
  genericfun(non.obj),
  genericfun(obj),
  genericfun.default(non.obj)
)

制作(注意,这显然会降低我的Windows系统上microbenchmark的精度):

Unit: nanoseconds
                        expr  min   lq median   uq   max neval
         genericfun(non.obj)  367  732  733.0 1098 16457   100
             genericfun(obj) 6217 6583 6583.0 6949 35838   100
 genericfun.default(non.obj)    0    1  183.5  366  1463   100

通用调度现在已降至不到一微秒。

此外,在递归使用的情况下,只有在C中的is.object的求值返回TRUE时才需要调用R泛型,因此仅包含非对象的递归结构不需要从C退出任何一点。

这种方法可能存在许多问题(并非最不可能的是它的可怕性),这些问题随着使用而变得明显,但这是我现在能想到的最好的问题。立即想到的一个问题是隐式类的调度不会发生(例如is.object(matrix()) == FALSE)。