了解如何在 R 中处理 .Internal C 函数

Posted

技术标签:

【中文标题】了解如何在 R 中处理 .Internal C 函数【英文标题】:Understanding how .Internal C functions are handled in R 【发布时间】:2013-11-08 22:21:38 【问题描述】:

我想知道是否有人可以向我说明 R 如何从在控制台提示符下键入的 R 命令执行 C 调用。我对R 对 a) 函数参数和 b) 函数调用本身的处理感到特别困惑。

让我们举个例子,在这种情况下set.seed()。想知道它是如何工作的,我在提示符下输入名称,获取源(look here for more on that),看到最终有一个.Internal(set.seed(seed, i.knd, normal.kind),所以尽职尽责地在/src/names.c.Internals 部分查找相关函数名称,发现它被称为do_setseed 并且在RNG.c 中,这导致我...

SEXP attribute_hidden do_setseed (SEXP call, SEXP op, SEXP args, SEXP env)

    SEXP skind, nkind;
    int seed;

    checkArity(op, args);
    if(!isNull(CAR(args))) 
    seed = asInteger(CAR(args));
    if (seed == NA_INTEGER)
        error(_("supplied seed is not a valid integer"));
     else seed = TimeToSeed();
    skind = CADR(args);
    nkind = CADDR(args);
    //...
      //DO RNG here 
    //...
    return R_NilValue;

什么是CARCADRCADDR?我的研究使我相信它们是受Lisp 影响的关于列表的构造,但除此之外,我不明白这些函数的作用或为什么需要它们checkArity() 有什么作用? SEXP args 似乎不言自明,但这是 在函数调用中传递的参数? SEXP op 代表什么?我将其理解为运算符(例如在 + 等二进制函数中),那么 SEXP call 是什么意思?

有没有人能够理解我打字时发生的事情

set.seed(1)

在 R 控制台提示符处,直到定义了 skindnkind ?我发现我无法很好地理解这个级别的源代码以及从解释器到 C 函数的路径。

【问题讨论】:

您可能想从adv-r.had.co.nz/C-interface.html 开始 - 我很快就会为这个特定的例子写一些更多的细节。 @hadley 谢谢,今晚我会经历这个。我真的很期待你的书出版!很快,我希望。 :-) @SimonO101:我会在你的问题标题中添加“... in R” @OlivierDulac -- 完成,谢谢。对于仅使用r 标签标记的问题,我们通常对此不以为然,但由于这也出现在c 标签下,我认为这是有道理的。 【参考方案1】:

基本的 C 级对列表提取函数是 CARCDR。 (对列表与列表非常相似,但实现为链表并在内部用于参数列表)。它们有简单的 R 等价物:x[[1]]x[-1]。 R 还提供了很多两者的组合:

CAAR(x) = CAR(CAR(x)) 相当于 x[[1]][[1]] CADR(x) = CAR(CDR(x)) 等价于x[-1][[1]],即x[[2]] CADDR(x) = CAR(CDR(CDR(x)) 等价于x[-1][-1][[1]],即x[[3]] 等等

访问对列表的第 n 个元素是 O(n) 操作,这与访问列表的第 n 个元素 O(1) 不同。这就是为什么没有更好的函数来访问对列表的第 n 个元素的原因。

内部/原始函数不进行名称匹配,它们只使用位置匹配,这就是为什么它们可以使用这个简单的系统来提取参数。

接下来您需要了解 C 函数的参数是什么。我不确定这些记录在哪里,所以我可能对结构并不完全正确,但我应该是一般的部分:

call:完整的调用,可能被match.call()捕获

op:从 R 调用的 .Internal 函数的索引。这是必需的,因为存在从 .Internal 函数到 C 函数的多对一映射。 (例如 do_summary 实现 sum、mean、min、max 和 prod)。该数字是 names.c 中的第三个条目 - do_setseed 始终为 0,因此从未使用过

args:提供给函数的参数对列表。

env:调用函数的环境。

checkArity 是一个调用Rf_checkArityCall 的宏,它基本上查找参数的数量(names.c 中的第五列是arity)并确保提供的数字匹配。您必须通过 C 中的许多宏和函数来查看发生了什么 - 拥有可以通过 grep 浏览的 R 源的本地副本非常有帮助。

【讨论】:

+1 谢谢!那么使用say CADDR(x) 是否有效地将访问时间保持在O(1) (我原以为这样的操作的时间损失会很小,因为我想不出一个参数列表会那个长吗?现在我觉得更有意义了,回顾一下我上面的例子(结合我目前正在阅读的你的书章节)。 @SimonO101 不,CADDR(x) 会比 CDR(x) 慢,但您可以像 Joshua 的示例中一样使用 CARCDR 来避免开销。【参考方案2】:

CARCDR 是您访问pairlist 对象的方式,如section 2.1.11 of R Language Definition 中所述。 CAR 包含第一个元素,CDR 包含其余元素。 section 5.10.2 of Writing R Extensions中给出了一个例子:

#include <R.h>
#include <Rinternals.h>

SEXP convolveE(SEXP args)

    int i, j, na, nb, nab;
    double *xa, *xb, *xab;
    SEXP a, b, ab;

    a = PROTECT(coerceVector(CADR(args), REALSXP));
    b = PROTECT(coerceVector(CADDR(args), REALSXP));
    ...

/* The macros: */
first = CADR(args);
second = CADDR(args);
third = CADDDR(args);
fourth = CAD4R(args);
/* provide convenient ways to access the first four arguments.
 * More generally we can use the CDR and CAR macros as in: */
args = CDR(args); a = CAR(args);
args = CDR(args); b = CAR(args);

还有一个TAG 宏来访问赋予实际参数的名称。

checkArity 确保传递给函数的参数数量是正确的。 args 是传递给函数的实际参数。 op 是偏移量指针“用于处理多个 R 函数的 C 函数”(引自 src/main/names.c,其中还包含显示每个函数的偏移量和数量的表)。

例如,do_colsum 处理 col/rowSumscol/rowMeans

/* Table of  .Internal(.) and .Primitive(.)  R functions
 * =====     =========        ==========
 * Each entry is a line with
 *
 *  printname  c-entry     offset  eval  arity   pp-kind   precedence  rightassoc
 *  ---------  -------     ------  ----  -----   -------   ----------  ----------
"colSums",    do_colsum,  0,      11,   4,     PP_FUNCALL, PREC_FN,  0,
"colMeans",   do_colsum,  1,      11,   4,     PP_FUNCALL, PREC_FN,  0,
"rowSums",    do_colsum,  2,      11,   4,     PP_FUNCALL, PREC_FN,  0,
"rowMeans",   do_colsum,  3,      11,   4,     PP_FUNCALL, PREC_FN,  0,

请注意,上表中的arity 是4,因为(尽管rowSums 等人只有3 个参数)do_colsum 有4,您可以从.Internal 调用rowSums 中看到:

> rowSums
function (x, na.rm = FALSE, dims = 1L) 

    if (is.data.frame(x)) 
        x <- as.matrix(x)
    if (!is.array(x) || length(dn <- dim(x)) < 2L) 
        stop("'x' must be an array of at least two dimensions")
    if (dims < 1L || dims > length(dn) - 1L) 
        stop("invalid 'dims'")
    p <- prod(dn[-(1L:dims)])
    dn <- dn[1L:dims]
    z <- if (is.complex(x)) 
        .Internal(rowSums(Re(x), prod(dn), p, na.rm)) + (0+1i) * 
            .Internal(rowSums(Im(x), prod(dn), p, na.rm))
    else .Internal(rowSums(x, prod(dn), p, na.rm))
    if (length(dn) > 1L) 
        dim(z) <- dn
        dimnames(z) <- dimnames(x)[1L:dims]
    
    else names(z) <- dimnames(x)[[1L]]
    z

【讨论】:

+1 谢谢!这很棒。所以checkArity 有效地计算函数调用中的参数数量,查找函数并计算函数期望的参数数量,如果它们不匹配,则会抛出受控错误以优雅地退出函数而不是调用函数参数数量错误并且可能存在段错误? @SimonO101:是的,它从names.c 的表中查找函数,以查看需要或允许多少个参数。您可以通过调用类似以下内容来查看错误:x &lt;- 1; .Internal(rowSums(x))

以上是关于了解如何在 R 中处理 .Internal C 函数的主要内容,如果未能解决你的问题,请参考以下文章

R语言系列之3-----文件读写

com.android.internal.R.styleable在哪里找到?

com.android.internal.R包怎么导入android里面的。

如何使用com.sun.org.apache.xerces.internal.parsers.SAXParser在SAXBuilder中禁用XML外部实体(XEE)处理

C语言试题七十七之请编写函实现渔夫打鱼晒网问题

C语言试题七十七之请编写函实现渔夫打鱼晒网问题