1

简而言之:如何从 Rccp C++ 代码中调用当用户使用基本 Ragrep中的常规函数​​时调用的 C 内部函数?agrep

长篇大论:我在这里发现了多个关于如何从 Rcpp 中调用为另一个包创建的 C 或 C++ 函数的问题(例如,使用 Rcpp 和Rcpp中其他包中的C 函数:从 Rcpp 中的包调用 C 函数)。

然而,我试图实现的事情同时更简单,但记录也更少:它是从 Rcpp 中直接调用一个 .Internal C 函数,该函数随基础 R 而不是另一个包提供,无需接口使用 R (即,不执行Rcpp 中的 Call R 函数中所说的操作)。对于位于基本 R 的 agrep 包装器下方的 .Internal C 函数,我该如何做到这一点?

我在这里尝试调用的特定函数是 agrep 内部 C 函数。对于上下文,我最终要实现的是加快对 agrep 的调用,以便在必须针对数百万个 x 目标中的每一个检查数百万个模式时。

4

1 回答 1

4

好问题。它的长短是“你不能”(在许多情况下),除非该函数在“src/include/”的头文件之一中可见。至少没那么容易。

不久前,我遇到了一个类似的有趣挑战,我尝试访问该do_docall函数(由 调用do.call),这不是一项简单的任务。首先,不可能直接#include <agrep.c>(或类似的东西)。该文件根本无法包含,因为它不是“src/include”的一部分。它被编译并且未编译的文件被删除(更不用说永远不应该“包含” .c 文件)。

如果一个人愿意走一英里,那么下一步可以看的是“复制”和“修改”源代码。基本上在“src/main/agrep.c”中找到该函数,将其复制到您的包中,然后修复您发现的任何错误。

这种方法的问题:

  1. 正如不公开R-exts的内部结构中所述(这是 R 中所有对象的基本结构)。sexprec_info许多内部函数使用此结构中的字段,因此必须将结构“复制”到您的源代码中,以便专门将其公开给您的代码。
  2. 如果您#include <Rcpp.h>在此文件之前使用过,则需要完成对内部函数的每次调用,并可能添加R_Rf_.
  3. 该函数可能包含对其他“内部”函数的调用,需要进一步复制和更改才能使其工作。
  4. 您还需要清楚地了解CDR,CAR和类似的功能。内部函数有一个文档化的结构,其中第一个参数包含传递给函数的完整调用,而像这 2 个函数这样的函数用于访问部分调用。我自己做了一个扎实的并重写了do_docall更改输入格式,以避免不得不考虑这一点。但这需要时间。另一种方法是根据文档创建 a pairlist,将其类型设置为 call-sexp (我现在不知道确切的名称)并为op,args和传递适当的参数env
  5. 最后,如果您通过这些步骤,发现有必要复制 的内部结构sexprec_info(如下所述),那么您将需要非常小心何时包含Rinternalsand Rcpp,因为其中任何一个都会导致您的代码如果您以错误的顺序包含标题和这些,则以最美丽和最安静的方式崩溃和燃烧!请注意,这甚至适用于[[Rcpp::export]],这可能确实会将它们包含在错误的任意顺序中!

如果您愿意走这么远,我建议您仔细阅读adv-R "R's C interface"R-ext 的第 2、5 和 6 章,甚至可能是R 内部手册,最后一旦完成查看do_docallfromsrc/main/coerce.c并将其与我的存储库cmdline.arguments/src/utils/{cmd_coerce.h, cmd_coerce.c}中的实现进行比较。在这个版本中,我有

  1. 添加了所有不公开的内部结构,以便我可以访问它们未修改的形式(当前会话未修改)。
    • 这包括用于存储当前使用SEXP的 's 的表,该表用作查找。这导致了一个问题,因为我无法访问修改后的版本,所以我的代码被宏阻止的旧代码稍微改变了#if --- defined(CMDLINE_ARGUMENTS_MAYBE_IN_THE_FUTURE)。幸运的是,导致问题的代码有一个静态答案,所以我可以解决这个问题(但情况可能并非总是如此)。
  2. 我添加了很多Rf_s 因为他们的宏版本不可用(因为我#include <Rcpp.h>在某个时候)
  3. 代码已被拆分为更小的函数,以使其更具可读性(为了我自己)。
  4. 该函数有一个额外的参数(名称),它没有在内部函数中使用,并带有一些额外的错误(针对我的特定需要)。

当我转移到另一个分支时,这个实现将“永远”被冻结(如果我想再次走这条路,这个实现将被冻结是为了我自己未来的利益)。

我花了几天时间在互联网上搜索这方面的信息,发现了 2 个不同的帖子,都在谈论如何实现这一点,而我的方法基本上是复制这个。这是否真的允许在 cran 包中,是另一个问题(而不是我将测试的问题)。

如果您想使用其他包中的非公共代码,则此方法再次适用。虽然通常在这里它就像“复制粘贴”他们的文件到您的存储库一样简单。

作为最后的附注,您提到的目的是在您必须执行数百万次调用时“加速”您的代码agrep。似乎这是一个应该考虑并行执行任务的时候。即使在完成上述步骤之后,创建 N 个并行会话来处理 K 次评估(例如 100.000)将是减少计算时间的第一步。当然,应该给每个会话一个批处理,而不是一次调用agrep.

于 2021-02-17T08:33:15.480 回答