学习如何编写用于R的C代码的最佳资源是什么?我知道R扩展的system and foreign language interfaces部分,但我觉得很难。编写与R一起使用的C代码有哪些好的资源(在线和离线)?
为了澄清,我不想学习如何编写C代码,我想学习如何更好地集成R和C.例如,我如何从C整数向量转换为R整数向量(或反之亦然)或从C标量到R矢量?
答案 0 :(得分:67)
好的老有使用来源,Luke! --- R本身有很多(非常有效的)可以学习的C代码,而且CRAN有数百个包,有些来自作者你相信。这提供了真实的,经过测试的例子来研究和适应。
但正如Josh所怀疑的那样,我更倾向于C ++,因此Rcpp。它也有很多例子。
编辑:我找到了两本有用的书:
也就是说,John正在发展Rcpp(和贡献),因为他发现R对象和C ++对象之间的匹配(通过Rcpp)非常自然 - 而ReferenceClasses在那里帮助。
编辑2:根据Hadley的重新定位问题,我非常强烈敦促您考虑使用C ++。有很多样板胡说八道,你必须要做C ---非常繁琐且非常可以避免。看看Rcpp-introduction vignette。另一个简单的例子是this blog post,我表明不是担心10%的差异(在Radford Neal的一个例子中),我们可以用C ++来增加八倍(当然是人为的例子。
编辑3:很复杂的是,您可能会遇到C ++错误,这些错误很明显很难理解。但是只是使用Rcpp 而不是扩展它,你几乎不需要它。虽然这个成本是不可否认的,但它更容易被更简单代码的好处黯然失色,更少的样板,没有PROTECT / UNPROTECT,没有内存管理等pp.Doug Bates就在昨天他说,他发现C ++和Rcpp更像是编写R而不是编写C ++。 YMMV等等。
答案 1 :(得分:53)
哈德利,
你绝对可以编写类似于C代码的C ++代码。
我理解你对C ++比C更复杂的说法。如果你想掌握一切:对象,模板,STL,模板元编程等等...大多数人不需要这些东西而且可以只是依靠别人来做。 Rcpp的实施非常复杂,但仅仅因为你不知道你的冰箱是如何工作的,这并不意味着你不能打开门并抓住新鲜的牛奶......
从你对R的许多贡献中,让我感到震惊的是你发现R有点乏味(数据处理,图形,字符串操作等等)。好用R的内部C API为更多的惊喜做好准备。这非常繁琐。
我不时阅读R-exts或R-ints手册。这有帮助。但是大部分时间,当我真的想要找到某些东西时,我会进入R源代码,也会进入由例如西蒙(那里通常有许多东西需要学习)。
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 ;
}
正如Dirk所说,在几个小插曲中还有其他例子。我们通常也会将人们指向我们的单元测试,因为他们每个测试代码的一个非常具体的部分,并且有点自我解释。
我显然有偏见,但我建议熟悉Rcpp而不是学习R的C API,然后如果某些内容不清楚或者看起来不适合使用Rcpp,请进入邮件列表。
无论如何,销售宣传结束。
我想这一切都取决于你最终要写的代码类型。
罗曼
答案 2 :(得分:28)
@hadley:遗憾的是,我没有特定的资源来帮助您开始使用C ++。我从Scott Meyers的书中找到了它(有效的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 static function:
给定值 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
然后,一旦你有了一个向量,最有用的是从中提取一个元素。这是通过operator []完成的,基于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的软件包中做了什么,检查小插图,单元测试,在邮件列表上回到我们这里。我们总是很乐意提供帮助。
答案 3 :(得分:19)
@jbremnant:没错。 Rcpp类实现了接近RAII模式的东西。创建Rcpp对象时,构造函数会采取适当的措施来确保底层R对象(SEXP)不受垃圾收集器的影响。析构函数撤销保护。这在Rcpp-intrduction小插图中有解释。底层实现依赖于R API函数 R_PreserveObject 和 R_ReleaseObject
由于C ++封装,确实存在性能损失。我们试图通过内联等方式将其保持在最低限度......惩罚很小,当您考虑到编写和维护代码所需的时间增益时,它就不那么重要了。
从Rcpp类调用R函数函数比使用C api直接调用eval要慢。这是因为我们采取预防措施并将函数调用包装到tryCatch块中,以便捕获R错误并将它们提升为C ++异常,以便可以使用C ++中的标准try / catch来处理它们。
大多数人都想使用矢量(特别是NumericVector),这个类的惩罚很小。 examples / ConvolveBenchmarks目录包含来自R-exts的臭名昭着的卷积函数的几种变体,并且晕图具有基准测试结果。事实证明,Rcpp比使用R API的基准代码更快。