我在哪里可以学习如何编写C代码来加速缓慢的R函数?

学习如何编写用于R的C代码的最佳资源是什么? 我知道R扩展的系统和外语接口部分,但我觉得很难。 用于编写与R一起使用的C代码的好资源(在线和离线)是什么?

为了澄清,我不想学习如何编写C代码,我想学习如何更好地整合R和C.例如,如何将C整数向量转换为R整数向量(反之亦然)或从C标量到R向量?

那么有好的老使用来源,卢克! — R本身有很多(非常有效的)可以学习的C代码,而CRAN有数百个包,有些来自你信任的作者。 这为学习和适应提供了真实的,经过testing的例子。

但是正如Josh所怀疑的那样,我更倾向于C ++,因此更倾向于Rcpp 。 它也有很多例子。

编辑:有两本书我发现有帮助:

  • 第一个是Venables和Ripley的“ S程序devise ”,虽然它在牙齿上变得很长(并且有多年的传言)。 那时候根本就没有别的东西。
  • Chambers的“ 数据分析软件 ”中的第二个更为新近,并且具有更好的R-centric感觉和两个扩展R的章节.C和C ++都被提到。 另外,约翰把我的文章做了细细的分解,这样一来就值得入场。

也就是说,John正在越来越喜欢Rcpp (和贡献),因为他发现R对象和C ++对象(通过Rcpp )之间的匹配是非常自然的 – 参考类在这里帮助。

编辑2:有了Hadley的重新考虑的问题,我强烈要求你考虑C ++。 有很多废话你必须做C – 非常乏味, 非常可以避免 。 看看Rcpp介绍小插曲 。 另一个简单的例子就是这个博客post ,我展示的不是担心10%的差异(在Radford Neal的一个例子中),我们可以用C ++(当然是一个人为的例子)增加八倍

编辑3:复杂的是,你可能会碰到C ++的错误,说得很温和,很难沟通。 但是要使用Rcpp而不是扩展它,你几乎不需要它。 虽然这个代价是不可否认的,但是它更简单的代码,更less的样板,没有PROTECT / UNPROTECT,没有内存pipe理等等的好处已经被黯然失色了。Doug Bates就在昨天说他发现C ++和Rcpp更像是写R比写C ++。 YMMV和所有这一切。

哈德利

您肯定可以编写类似于C代码的C ++代码。

我明白你说的C ++比C更复杂。这是如果你想掌握一切:对象,模板,STL,模板元编程等等…大多数人不需要这些东西,只能依靠其他到它。 Rcpp的实施是非常复杂的,但是因为你不知道你的冰箱是如何工作的,所以并不意味着你不能打开门来抢鲜牛奶。

从你对R的许多贡献中,令我感到震惊的是你觉得R有点乏味(数据操作,graphics,string操作等等)。 那么用R的内部C API准备好更多的惊喜。这是非常乏味的。

我不时阅读R-exts或R-ints手册。 这有帮助。 但是大多数情况下,当我真正想要了解某些东西的时候,我会进入R源,还有像Simon这样的软件包的来源(通常有很多东西要学习)。

Rcpp旨在使API的这些繁琐的方面消失。

基于几个例子,你可以自己判断你发现的更复杂,混淆等等。 这个函数使用C API创build一个字符向量:

 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,您可以编写与以下相同的function:

 SEXP foobar(){ return Rcpp::CharacterVector::create( "foo", "bar" ) ; } 

要么:

 SEXP foobar(){ Rcpp::CharacterVector res(2) ; res[0] = "foo" ; res[1] = "bar" ; return res ; } 

正如德克所说,在几个小插曲上还有其他的例子。 我们也经常将人们指向我们的unit testing,因为他们每个人都testing代码的一个非常具体的部分,并且有点自我解释。

我显然有偏见,但我build议熟悉Rcpp,而不是学习R的C API,然后来到邮件列表,如果有什么不清楚或似乎不适用于Rcpp。

无论如何,销售点的结束。

我想这一切都取决于你想写什么样的代码最终。

罗曼

@hadley:不幸的是,我没有具体的资源来帮助你开始使用C ++。 我从Scott Meyers的书(有效的C ++,更有效的C ++等)中select了它们,但这些并不是真正可以称为介绍的东西。

我们几乎完全使用.Call接口来调用C ++代码。 规则很简单:

  • C ++函数必须返回一个R对象。 所有R对象都是SEXP。
  • C ++函数使用0到65个R对象作为input(又是SEXP)
  • 它必须(不是真的,但我们可以保存这个以后)用C链接声明,或者用extern“C”或者Rcpp定义的RcppExport别名。

所以.Call函数在一些头文件中被这样声明:

 #include <Rcpp.h> RcppExport SEXP foo( SEXP x1, SEXP x2 ) ; 

并在.cpp文件中像这样实现:

 SEXP foo( SEXP x1, SEXP x2 ){ ... } 

关于使用Rcpp的R API没有太多的了解。

大多数人只想处理Rcpp中的数字向量。 你用NumericVector类来做到这一点。 有几种方法可以创build一个数字向量:

从您从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的软件包中做什么,检查小插曲,unit testing,回到我们的邮件列表中。 我们总是乐于帮助。

@jbremnant:是的。 Rcpp类实现了一些接近RAII模式的东西。 当创build一个Rcpp对象时,构造函数采取适当的措施来确保底层R对象(SEXP)不受垃圾收集器的影响。 析构者撤销保护。 这在Rcpp-intrduction小插曲中有解释。 底层实现依赖于R API函数R_PreserveObjectR_ReleaseObject

由于C ++封装,确实会有性能损失。 我们试图用内联等方法来保持这个最低限度。惩罚是很小的,当考虑到编写和维护代码所需要的时间,这是不相关的。

从Rcpp类函数调用R函数比用C api直接调用eval要慢。 这是因为我们采取了预防措施,将函数调用包装到tryCatch块中,以便捕获R错误并将它们提升为C ++exception,以便可以使用C ++中的标准try / catch处理它们。

大多数人想使用vector(特别是NumericVector),并且这个类的惩罚是非常小的。 示例/ ConvolveBenchmarks目录包含来自R-exts的臭名昭着的卷积函数的几个变体,并且小插曲具有基准testing结果。 事实certificate,Rcpp比使用R API的基准代码更快。