【问题标题】:Implementing S3 Generic/Methods in C在 C 中实现 S3 泛型/方法
【发布时间】:2023-03-20 01:46:01
【问题描述】:

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

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

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

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

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

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

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


编辑:为了解决马丁对特定示例的要求,我正在尝试编写一个函数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
}

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

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

【问题讨论】:

  • 你为什么要这样做?您确定您的瓶颈是 S3 调度吗?
  • 我不知道有什么方法可以在不调用 R 函数的情况下进行 S3 调度。正如 Josh 所说,您确定 S3 会减慢您的速度吗? S3 调度的成本大约为 1-2 微秒。最后,最近对 R 源代码的一些提交可以加快 S3 调度,它们应该会出现在下一个版本中。
  • @JoshuaUlrich,是的,我很确定。我的函数目前在 C 语言中的评估时间为 1.5 微秒(包括来自 R 的初始调用),并且每次 S3 调度会增加 2 微秒,其中可能有几个。该函数旨在尽可能优化,以便可以将其添加到作为拆分-应用-组合类型分析的一部分运行数百万次的 R 函数中,而不会过度影响整体执行时间。
  • @wch,感谢您对即将到来的 S3 改进的提醒。 “我不知道如何在不调用 R 函数的情况下进行 S3 调度”,您的意思是“无法从 C 中访问内部 R 方法”?
  • 很高兴看到一个明确的(简化的)示例;由于 S3 调度已经在 C 中,目前尚不清楚通过自己从 C 调用它(通过 R 函数调用的 C 级公式,正如我认为@wch 所考虑的那样)可以获得什么。但也许还有其他解决方案,就像记忆和我们的老朋友矢量化可能分裂之前,这会有所帮助。

标签: r dispatch


【解决方案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)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多