学习如何编写用于 R 的 C 代码的最佳资源是什么?我知道 R 扩展的系统和外语接口部分,但我觉得很难。用于编写与 R 一起使用的 C 代码的好资源(在线和离线)是什么?
澄清一下,我不想学习如何编写 C 代码,我想学习如何更好地集成 R 和 C。例如,如何从 C 整数向量转换为 R 整数向量(反之亦然)还是从 C 标量到 R 向量?
好吧,有个好老使用来源,卢克!--- R 本身有很多(非常有效的)C 代码可供学习,CRAN 有数百个包,其中一些来自您信任的作者。这提供了真实的、经过测试的例子来学习和适应。
但正如 Josh 所怀疑的那样,我更倾向于 C++,因此更倾向于Rcpp。它也有很多例子。
编辑:有两本书我觉得很有帮助:
也就是说,John 越来越喜欢Rcpp(并做出贡献),因为他发现 R 对象和 C++ 对象(通过Rcpp)之间的匹配非常自然——而 ReferenceClasses 对此有所帮助。
编辑 2: 对于 Hadley 重新聚焦的问题,我强烈建议您考虑 C++。与 C 相关的样板废话太多了——非常乏味且非常可避免。看看Rcpp-introduction vignette。另一个简单的例子是这篇博客文章,我展示了不用担心 10% 的差异(在 Radford Neal 的一个例子中),我们可以使用 C++ 获得80 倍的增长(当然这是一个人为的例子)。
编辑 3:您可能会遇到 C++ 错误,这很复杂,说得委婉些,很难理解。但是仅仅使用 Rcpp而不是扩展它,你几乎不需要它。虽然这个成本是不可否认的,但它的好处远远超过了更简单的代码、更少的样板、没有保护/取消保护、没有内存管理等 pp。Doug Bates 昨天表示他发现 C++ 和 Rcpp 更像是编写 R比写 C++。YMMV 等等。
哈德利,
您绝对可以编写类似于 C 代码的 C++ 代码。
我理解你所说的 C++ 比 C 更复杂的说法。如果你想掌握一切:对象、模板、STL、模板元编程等......大多数人不需要这些东西,只能依赖其他东西给它。Rcpp的实现很复杂,但是你不知道你的冰箱是怎么工作的,并不代表你不能开门拿鲜奶……
从您对 R 的许多贡献中,让我印象深刻的是您发现 R 有点乏味(数据操作、图形、字符串操作等......)。用 R 的内部 C API 为更多惊喜做好准备。这非常乏味。
我不时阅读 R-exts 或 R-ints 手册。这有帮助。但大多数时候,当我真的想了解一些东西时,我会进入 R 源代码,以及例如 Simon 编写的包的源代码(那里通常有很多东西要学)。
Rcpp 旨在消除 API 的这些繁琐方面。
您可以根据一些示例自行判断您发现更复杂、更模糊等的内容。此函数使用 C API 创建一个字符向量:
SEXP foobar(){
SEXP ab;
PROTECT(ab = allocVector(STRSXP, 2));
SET_STRING_ELT( ab, 0, mkChar("foo") );
SET_STRING_ELT( ab, 1, mkChar("bar") );
UNPROTECT(1);
}
使用 Rcpp,您可以编写与以下相同的函数:
SEXP foobar(){
return Rcpp::CharacterVector::create( "foo", "bar" ) ;
}
或者:
SEXP foobar(){
Rcpp::CharacterVector res(2) ;
res[0] = "foo" ;
res[1] = "bar" ;
return res ;
}
正如德克所说,在几个小插曲上还有其他例子。我们通常还会将人们引向我们的单元测试,因为他们每个人都测试代码的一个非常具体的部分,并且在某种程度上是不言自明的。
我在这里显然有偏见,但我建议熟悉 Rcpp 而不是学习 R 的 C API,然后如果有不清楚的地方或 Rcpp 似乎不可行,请访问邮件列表。
无论如何,销售宣传结束。
我想这一切都取决于您最终要编写什么样的代码。
罗曼
@hadley:不幸的是,我没有具体的资源来帮助您开始使用 C++。我是从 Scott Meyers 的书(Effective C++、更有效的 C++ 等)中挑选出来的,但这些并不是真正可以称之为介绍性的。
我们几乎只使用 .Call 接口来调用 C++ 代码。规则很简单:
所以一个 .Call 函数在一些头文件中被这样声明:
#include <Rcpp.h>
RcppExport SEXP foo( SEXP x1, SEXP x2 ) ;
并在 .cpp 文件中像这样实现:
SEXP foo( SEXP x1, SEXP x2 ){
...
}
关于使用 Rcpp 的 R API 没有更多的了解。
大多数人只想处理 Rcpp 中的数字向量。您可以使用 NumericVector 类执行此操作。有几种方法可以创建数值向量:
从您从 R 传递下来的现有对象:
SEXP foo( SEXP x_) {
Rcpp::NumericVector x( x_ ) ;
...
}
使用 ::create 静态函数给定值:
Rcpp::NumericVector x = Rcpp::NumericVector::create( 1.0, 2.0, 3.0 ) ;
Rcpp::NumericVector x = Rcpp::NumericVector::create(
_["a"] = 1.0,
_["b"] = 2.0,
_["c"] = 3
) ;
给定尺寸:
Rcpp::NumericVector x( 10 ) ; // filled with 0.0
Rcpp::NumericVector x( 10, 2.0 ) ; // filled with 2.0
然后一旦你有了一个向量,最有用的就是从中提取一个元素。这是通过操作符 [] 完成的,具有基于 0 的索引,因此例如对数字向量的值求和如下所示:
SEXP sum( SEXP x_ ){
Rcpp::NumericVector x(x_) ;
double res = 0.0 ;
for( int i=0; i<x.size(), i++){
res += x[i] ;
}
return Rcpp::wrap( res ) ;
}
但是使用 Rcpp 糖,我们现在可以做得更好:
using namespace Rcpp ;
SEXP sum( SEXP x_ ){
NumericVector x(x_) ;
double res = sum( x ) ;
return wrap( res ) ;
}
正如我之前所说,这完全取决于您要编写什么样的代码。查看人们在依赖 Rcpp 的软件包中做了什么,检查小插图,单元测试,在邮件列表中返回给我们。我们总是很乐意提供帮助。
@jbremnant:没错。Rcpp 类实现了一些接近 RAII 模式的东西。创建 Rcpp 对象时,构造函数会采取适当的措施来确保底层 R 对象 (SEXP) 免受垃圾收集器的影响。析构函数撤销保护。这在Rcpp-intrduction小插图中进行了解释。底层实现依赖于 R API 函数R_PreserveObject和R_ReleaseObject
由于 C++ 封装,确实存在性能损失。我们尝试通过内联等将其保持在最低限度……代价很小,而且当您考虑到编写和维护代码所需的时间方面的收益时,它就没有那么重要了。
从 Rcpp 类 Function 调用 R 函数比使用 C api 直接调用 eval 慢。这是因为我们采取了预防措施并将函数调用包装到 tryCatch 块中,以便我们捕获 R 错误并将它们提升为 C++ 异常,以便可以使用 C++ 中的标准 try/catch 处理它们。
大多数人都想使用向量(特别是 NumericVector),而且这个类的惩罚非常小。examples/ConvolveBenchmarks 目录包含来自 R-exts 的臭名昭著的卷积函数的几个变体,并且小插图具有基准测试结果。事实证明,Rcpp 比使用 R API 的基准代码更快。