在哪里可以学习如何编写C代码来加快慢速的R函数?[关闭]


115

学习如何编写用于R的C代码的最佳资源是什么?我了解系统和外语界面 R扩展部分,但是我发现很难。有什么好的资源(在线和离线)用于编写与R一起使用的C代码?

澄清一下,我不想学习如何编写C代码,而是想学习如何更好地集成R和C。例如,如何将C整数向量转换为R整数向量(反之亦然)还是从C标量到R向量?

Answers:


71

好吧,有个好老的使用来源,卢克!--- R本身具有很多可以学习的(非常有效的)C代码,并且CRAN有数百个程序包,其中一些来自您信任的作者。这提供了经过测试的真实示例,可以进行研究和调整。

但是正如乔什(Josh)所怀疑的那样,我更倾向于C ++以及Rcpp。它还有很多示例。

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

  • 第一个是Venables和Ripley的“ S编程 ”,尽管它步入正轨(并且多年来一直有第二版的传言)。当时根本没有其他东西。
  • 钱伯斯的“ 数据分析软件 ”中的第二篇,它是最新的,并且具有更好的以R为中心的感觉-以及有关扩展R的两章。提到了C和C ++。另外,约翰将我的消化方法粉碎了,因此仅凭这个价格就值得入场。

也就是说,John越来越喜欢Rcpp(并做出了贡献),因为他发现R对象和C ++对象(通过Rcpp)之间的匹配非常自然-引用类对此提供了帮助。

编辑2:关于 Hadley重新关注的问题,我强烈建议您考虑使用C ++。与C有关的样板废话太多了-非常繁琐且非常可避免。看看Rcpp简介小插图。另一个简单的示例是此博客文章,我在这里展示了,不用担心10%的差异(在Radford Neal的示例中),我们可以使用C ++ 获得八十倍的增长(当然是人为的示例)。

编辑3:复杂性在于,您可能会遇到C ++错误,总之,很难理解。但是,仅使用Rcpp而不是对其进行扩展,就几乎不需要。尽管这笔费用是不可否认的,但它的优点却因为简单的代码,更少的样板,没有PROTECT / UNPROTECT,没有内存管理等优点而黯然失色。DougBates昨天表示,他发现C ++和Rcpp更像是编写R而不是编写C ++。YMMV等。


我希望得到一个“ use Rcpp”答案;)如果您能阐明使用C ++而不是C的缺点,那将非常有用。一个主要的方面似乎是C ++比C-要复杂得多这使它更难使用吗?(或者在实践中,您可以编写与C非常相似的C ++代码吗?)我也将感谢更多针对不熟悉现有C api的新用户的参考资料。
hadley 2010年

2
编辑3是的,可以。Meyers将C ++称为“四种范例”语言,您不必全部使用这四种语言。将其用作“更好的C”并使用Rcpp作为R的胶水是完全可以的。没有人对您施加任何风格–这不是Java ;-)
Dirk Eddelbuettel

@Dirk:详细说明。以前在我们办公室提出了一个问题,因为在这里通常使用C代替C ++。什么时候在C ++上使用C会有益,或者您只是说“从不C,总是C ++”?
Joris Meys 2010年

哈德利:好酷。我们会对您的反馈意见非常感兴趣。请加入rcpp-devel,不要退缩。我们知道我们的文档很短-但新的眼光可以极大地帮助您。
Dirk Eddelbuettel

6
@hadley表示我们可以期望在速度方面有所改善ggplot吗?
aL3xa 2011年

56

哈德利

您绝对可以编写类似于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 ;
}

正如Dirk所说,在几个小插曲上还有其他示例。我们通常也将人们引向我们的单元测试,因为他们每个人都测试代码的非常具体的部分,并且有些自我解释。

我在这里显然有偏见,但是我建议您熟悉Rcpp而不是学习R的C API,如果不清楚或似乎无法使用Rcpp,然后进入邮件列表。

无论如何,结束销售。

我想这取决于您最终要编写哪种代码。

罗曼


2
“ Rcpp旨在使API的这些乏味的方面消失” =正是我想要的。谢谢!真正有用的是为熟悉C并想使用Rcpp的人提供简短的C ++入门。
哈德利2010年

很好,Rcpp的简短示例将我卖了。我假设allocXX和UNPROTECT(1)的处理方式很像智能指针如何管理资源。即RAII。通过在香草C api上使用Rcpp是否有明显的性能损失?
jbremnant 2010年

我们在Rcpp简介中通过一个基准示例(也位于源代码/已安装的软件包中)解决了这一问题。简而言之,一点都不罚款。
Dirk Eddelbuettel

29

@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静态函数给定值:

 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的程序包中做什么,检查小插图,进行单元测试,然后在邮件列表中返回给我们。我们总是很乐意提供帮助。


20

@jbremnant:是的。Rcpp类实现了一些类似于RAII模式的东西。创建Rcpp对象时,构造函数将采取适当的措施以确保基础R对象(SEXP)受垃圾回收器保护。析构函数撤消保护。在Rcpp插入插图中对此进行了说明。基础实现依赖于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的基准代码更快。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.