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

时间:2010-11-05 13:20:27

标签: r rcpp

学习如何编写用于R的C代码的最佳资源是什么?我知道R扩展的system and foreign language interfaces部分,但我觉得很难。编写与R一起使用的C代码有哪些好的资源(在线和离线)?

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

4 个答案:

答案 0 :(得分:67)

好的老有使用来源,Luke! --- R本身有很多(非常有效的)可以学习的C代码,而且CRAN有数百个包,有些来自作者你相信。这提供了真实的,经过测试的例子来研究和适应。

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

编辑:我找到了两本有用的书:

  • 第一个是Venables和Ripley的“ S Programming ”,尽管它已经很久了(并且有传闻第二版多年)。当时根本没有别的。
  • 钱伯斯的“数据分析软件”中的第二个,它更新近,并且具有更好的以R为中心的感觉 - 以及关于扩展R的两章。提到C和C ++ 。另外,约翰为我对digest所做的事情感到羞耻,因此仅凭入场价格就是值得的。

也就是说,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 ++代码。规则很简单:

  • C ++函数必须返回R对象。所有R对象都是SEXP。
  • C ++函数将0到65个R对象作为输入(再次为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类执行此操作。有几种方法可以创建数字向量:

从您从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的基准代码更快。