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

had*_*ley 111 r rcpp

学习如何编写用于R的C代码的最佳资源是什么?我知道R扩展的系统和外语接口部分,但我发现它很难.编写用于R的C代码有哪些好的资源(在线和离线)?

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

Dir*_*tel 68

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

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

编辑:有两本我觉得有用的书:

  • 第一个是Venables和Ripley的" S编程 ",尽管它已经很久了(并且有传闻第二版多年).当时没有别的东西.
  • 钱伯斯的" 数据分析软件 "中的第二个是更近期的,具有更好的以R为中心的感觉 - 以及关于扩展R的两章.提到了C和C++.另外,约翰粉碎了我对消化所做的事情,因此仅凭入学价格就是值得的.

也就是说,John喜欢Rcpp(和贡献),因为他发现R对象和C++对象(通过Rcpp)之间的匹配非常自然 - 而ReferenceClasses帮助那里.

Edit 2: With Hadley's refocussed question, I very strongly urge you to consider C++. There is so much boilerplate nonsense you have to do with C---very tedious and very avoidable. Have a look at the Rcpp-introduction vignette. Another simple example is this blog post where I show that instead of worrying about 10% differences (in one of the Radford Neal examples) we can get eightyfold increases with C++ (on what is of course a contrived example).

编辑3:很复杂的是,您可能会遇到C++错误,这些错误很明显很难理解.但是,只使用Rcpp而不是扩展它,你几乎不需要它.虽然这个成本是不可否认的,它远远由黯然失色利益的简单的代码,样板少,无保护/取消保护,没有内存管理等页.道格·贝茨就在昨天表示,他认为C++和RCPP要更喜欢写[R而不是写C++.YMMV等等.

  • @hadley是否意味着我们可以期待`ggplot`的一些速度提升? (6认同)
  • 请参阅*编辑3*和*是,您可以*.Meyers称C++是一种"四种范式",你不必全部使用这四种语言.使用它作为"只是一个更好的C"并使用Rcpp作为R的胶水是完全正常的.没有人强迫你的风格 - 这不是Java ;-) (2认同)

Rom*_*ois 54

哈德利

你绝对可以编写类似于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);
}
Run Code Online (Sandbox Code Playgroud)

使用Rcpp,您可以编写相同的函数:

SEXP foobar(){
   return Rcpp::CharacterVector::create( "foo", "bar" ) ;
}
Run Code Online (Sandbox Code Playgroud)

要么:

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

正如德克所说,在几个小插曲中还有其他例子.我们通常也会将人们指向我们的单元测试,因为他们每个测试代码的一个非常具体的部分,并且有点自我解释.

我显然有偏见,但我建议熟悉Rcpp,而不是学习R的C API,然后如果有些东西不清楚或者看起来不适合Rcpp那么就来到邮件列表.

无论如何,销售宣传结束.

我想这一切都取决于你最终要写的代码类型.

罗曼

  • "Rcpp旨在使这些乏味的API方面消失"=正是我正在寻找的东西.谢谢!对于熟悉C并希望使用Rcpp的人来说,真正有用的是简短的C++入门. (2认同)

Rom*_*ois 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 ) ;
Run Code Online (Sandbox Code Playgroud)

并在.cpp文件中实现如下:

SEXP foo( SEXP x1, SEXP x2 ){
   ...
}
Run Code Online (Sandbox Code Playgroud)

关于使用Rcpp的R API还有很多东西需要了解.

大多数人只想处理Rcpp中的数字向量.您可以使用NumericVector类执行此操作.有几种方法可以创建数字向量:

从您从R传递下来的现有对象:

 SEXP foo( SEXP x_) {
    Rcpp::NumericVector x( x_ ) ;
    ...
 }
Run Code Online (Sandbox Code Playgroud)

使用:: 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
 ) ;
Run Code Online (Sandbox Code Playgroud)

给定大小:

 Rcpp::NumericVector x( 10 ) ;      // filled with 0.0
 Rcpp::NumericVector x( 10, 2.0 ) ; // filled with 2.0
Run Code Online (Sandbox Code Playgroud)

然后,一旦你有了一个向量,最有用的是从中提取一个元素.这是通过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 ) ;
}
Run Code Online (Sandbox Code Playgroud)

但是使用Rcpp糖,我们现在可以做得更好:

using namespace Rcpp ;
SEXP sum( SEXP x_ ){
   NumericVector x(x_) ;
   double res = sum( x ) ;
   return wrap( res ) ;
}
Run Code Online (Sandbox Code Playgroud)

正如我之前所说,这完全取决于你想要编写什么类型的代码.看看人们在依赖Rcpp的软件包中做了什么,检查小插图,单元测试,在邮件列表上回到我们这里.我们总是很乐意提供帮助.


Rom*_*ois 19

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

由于C++封装,确实存在性能损失.我们试图通过内联等方式将其保持在最低限度......惩罚很小,当您考虑到编写和维护代码所需的时间增益时,它就不那么重要了.

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

大多数人都想使用矢量(特别是NumericVector),这个类的惩罚很小.examples/ConvolveBenchmarks目录包含来自R-exts的臭名昭着的卷积函数的几种变体,并且晕图具有基准测试结果.事实证明,Rcpp比使用R API的基准代码更快.