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

学习如何编写与 R 一起使用的 C 代码的最佳资源是什么?我知道 R 扩展的 系统和外语接口部分,但是我发现它非常难。有什么好的资源(两个在线和离线)可以用来编写与 R 一起使用的 C 代码?

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

9821 次浏览

Well there is the good old 利用源头,卢克! --- R itself has plenty of (very efficient) C code one can study, and CRAN has hundreds of packages, some from authors you trust. That provides real, tested examples to study and adapt.

但正如 Josh 所怀疑的,我更倾向于 C + + ,因此也更倾向于 Rcpp

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

  • 第一个是维纳布尔斯和雷普利的“ 编程”,尽管它已经过了很长的时间(多年来一直有传言说第二版)。在那个时候,除此之外什么都没有。
  • 钱伯斯的“ 数据分析软件”中的第二个更新,有一个更好的以 R 为中心的感觉——有两个章节关于扩展 R,C 和 C + + 都被提到。此外,约翰撕碎我为我做了什么与 消化,所以单是入学的价格是值得的。

也就是说,John 越来越喜欢 Rcpp(并且做出了贡献) ,因为他发现 R 对象和 C + + 对象之间的匹配(通过 Rcpp)是非常自然的—— ReferenceClass 在这方面有所帮助。

编辑2: 对于 Hadley 重新定位的问题,我建议你考虑一下 C + + 。有很多样板的废话,你必须做的 C-非常乏味和 完全可以避免。看看 Rcpp-简介。另一个简单的例子是 这篇博文,我展示了我们可以用 C + + 增加 八十倍,而不用担心10% 的差异(在 Radford Neal 的一个例子中)。

编辑3: 复杂性在于,你可能会遇到 C + + 错误,说得委婉一点,这些错误很难理解。但是仅仅使用 使用 Rcpp而不是扩展它,你几乎不需要它。虽然这个 成本是不可否认的,它远远超过了 利益的简单代码,更少的样板,没有 PROTECT/UNPROTECT,没有内存管理等 pp。Doug Bates 昨天说他发现 C + + 和 Rcpp 更像是编写 R 而不是编写 C + + 。YMMV 什么的。

哈德利,

您完全可以编写类似于 C 代码的 C + + 代码。

I understand what you say about C++ being more complicated than C. This is if you want to master everything : objects, templates, STL, template meta programming, etc ... most people don't need these things and can just rely on others to it. The implementation of Rcpp is very complicated, but just because you don't know how your fridge works, it does not mean you cannot open the door and grab fresh milk ...

从您对 R 的许多贡献中,我感到惊讶的是您发现 R 有些乏味(数据操作、图形、字符串操作等等)。那么就为 R 的内部 C API 带来的更多惊喜做好准备吧。这太无聊了。

我时不时地阅读 R-ext 或 R-int 手册。这有帮助。但是大多数时候,当我真的想了解一些东西的时候,我会查看 R 源代码,也会查看例如 Simon 编写的包的源代码(那里通常有很多东西需要学习)。

Rcpp 的设计目的是消除 API 的这些乏味方面。

You can judge for yourself what you find more complicated, obfuscated, etc ... based on a few examples. This function creates a character vector using the 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 ;
}

正如德克所说,在这几个小插图中还有其他的例子。我们通常还会将人们引向我们的单元测试,因为每个单元测试都测试代码的一个非常特定的部分,并且在某种程度上是不言自明的。

I'm obviously biased here, but I would recommend getting familiar about Rcpp instead of learning the C API of R, and then come to the mailing list if something is unclear or does not seem doable with Rcpp.

不管怎样,推销结束了。

我想这完全取决于您最终想要编写什么类型的代码。

罗曼

@ jbremant: 没错。Rcpp 类实现了一些类似于 RAII 模式的东西。在创建 Rcpp 对象时,构造函数采取适当的措施确保底层 R 对象(SEXP)不受垃圾收集器的影响。破坏者撤销保护。这在 Rcpp-intrduction小插图中有解释。底层实现依赖于 R API 函数 R _ Preserve veObjectR _ ReleaseObject

由于 C + + 封装,确实存在性能损失。我们试图保持这在最低限度与内联等... 罚款很小,当你考虑到的收益方面的时间,它需要编写和维护代码,这并不那么相关。

Calling R functions from the Rcpp class Function is slower than directly calling eval with the C api. This is because we take precautions and wrap the function call into a tryCatch block so that we capture R errors and promote them to C++ exceptions so that they can be dealt with using the standard try/catch in C++.

大多数人都希望使用矢量(特别是 NumericVector) ,这个类的代价非常小。示例/ConvolveBenchmarks 目录包含来自 R-exts 的臭名昭著的卷积函数的几个变体,并且插图具有基准测试结果。事实证明,Rcpp 使其比使用 RAPI 的基准代码更快。

@ hadley: 不幸的是,我没有具体的资源来帮助你开始学习 C + + 。我是从 Scott Meyers 的书(有效的 C + + ,更有效的 C + + ,等等)中学到的,但是这些并不是所谓的入门书籍。

我们几乎完全使用.Call 接口来调用 C + + 代码:

  • C + + 函数必须返回一个 R 对象,所有的 R 对象都是 SEXP。
  • C + + 函数接受0到65R 对象作为输入(同样是 SEXP)
  • 它必须使用 C 链接声明(实际上并非如此,但我们可以将其保存到以后) ,或者使用 Rcpp 定义的 外部 CRcppExport别名。

So a .Call function gets declared like this in some header file:

#include <Rcpp.h>


RcppExport SEXP foo( SEXP x1, SEXP x2 ) ;

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

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

关于使用 Rcpp 的 R API,没有更多的信息需要了解。

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

From an existing object that you pass down from 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

Then once you have a vector, the most useful thing is to extract one element from it. This is done with the operator[], with 0-based indexing, so for example summing values of a numeric vector goes something like this:

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 的软件包中做什么,检查小插图、单元测试,然后在邮件列表中返回给我们。我们很乐意帮忙。