Rcpp的前世今生
今年六月,Springer useR系列新出了一本,Seamless R and C++ Integration with Rcpp, 这可能是唯一的一本Rcpp完整教程。Rcpp几乎可以认为是R语言的一个里程碑,而其最大的特点就是那本书标题里的那个词“Seamless”。R本身 自带了C语言接口,但并不是那么好用,尤其是涉及内存管理的时候,而Rcpp成功的解决了这个问题,实现了“无缝链接”。
Rcpp作者们
既然说了Rcpp是R语言的一个里程碑,我们先看看Rcpp的作者们,这几乎是现有最强大的一个package团队。下面按照Rcpp网站上的顺序依次介绍。
Dirk Eddelbuettel
Rcpp现在的主要维护者之一,就是Springer那本书的作者,Ketchum Trading公司高级量化分析师,资深quant。Dirk是Debian/Ubuntu下R的维护者,R/Finance会议发起人之一,其个人博客上有历次会议上报告的slide,R社区重要技术网站之一。
Romain Francois
Rcpp现在的主要维护者之一,独立的R开发者和咨询师,自我认定是“Professional R Enthusiast”,其博客也是圈内重要技术博客之一。
Douglas Bates
Rcpp作者之一,Wisconsin-Madison大学统计系荣休教授,bioconductor创始人。
John Chambers
S语言的创造者,现为Stanford大学顾问教授,美国统计学会院士,R语言核心团队成员之一。Chambers于1998因S语言获得ACM Software System Award,这是软件界最高奖项之一,1999年授予Apache Group,1995年授予World Wide Web。
JJ Allaire
RStudio创始人,著名的ColdFusion工具和Windows Live Writer也是其作品之一。
Rcpp大事记
2005年,Rcpp作为RQuantlib的一部分出现,作者为Dominick Samperi;
2006年,Rcpp在CRAN上发布,后更名为RcppTemplate;
2008年,Dirk决定重写Rcpp,并陆续发布新版本,老版本API作为RcppClassic继续开发维护;
2009年,RcppTemplate正式放弃维护,进入CRAN存档;
2009年,Dirk和Franois重新设计Rcpp,并发布新版本;
2013年,CRAN中基于Rcpp开发的package已超过100个。
Rcpp实例
这里用的是Dirk书中的一个很简单的例子:Fibonacci数列。这玩意太简单了,我们直接看代码就好了。
如果用R写,很简单的一个实现如下:
fibR<-function(n){
first <- 0
second <- 1
third <- 0
for(i in seq_len(n)){
third <- first + second
first <- second
second <- third
}
return(first)
}
C++的实现如下,其实和普通的C++相比,只是多了一个类型转换而已。其中SEXP是pointer to S expression type,这个是指向R各种类型的一个指针。我不知道各位对“指针”什么感情,反正听到这两个字,如果再涉及内存管理,我就有一个生理性的恐惧,而 Rcpp的伟大之处就在于解决了这个问题,这个后面会提到。
#include
int fib(const int x){
int first = 0;
int second = 1;
int third = 0;
for(int i = 1; i <= x; i++){
third = first + second;
first = second;
second = third;
}
return first;
}
extern "C" SEXP fibWrapper(SEXP xs){
int x = Rcpp::as(xs);
int result = fib(x);
return (Rcpp::wrap(result));
}
编译C++文件从而能够让R调用的方法一般有两种,如果在linux环境下,设置环境变量,命令如下:
$ export PKG_LIBS=`Rscript -e "Rcpp:::LdFlags()"`
$ export PKG_CXXFLAGS=`Rscript -e "Rcpp:::CxxFlags()"`
$ R CMD SHLIB myfile.cpp
还有一种跨平台的方法,windows下只要设置好环境变量也没问题的玩法:
$ Rscript -e "Rcpp:::SHLIB('myfile.cpp')"
其实只要看一下输出结果,就明白了,其实自己手动写完整的命令也不是不行
$ Rscript -e "Rcpp:::SHLIB('fibWrapper.cpp')"
$ g++ -I/usr/share/R/include -DNDEBUG -I/home/kouqiang/R/x86_64-pc-linux-gnu-library/3.0/Rcpp/include -fpic -O3 -pipe -g -c fibWrapper.cpp -o fibWrapper.o
$ g++ -shared -o fibWrapper.so fibWrapper.o -L/home/kouqiang/R/x86_64-pc-linux-gnu-library/3.0/Rcpp/lib -lRcpp -Wl,-rpath,/home/kouqiang/R/x86_64-pc-linux-gnu-library/3.0/Rcpp/lib -L/usr/lib/R/lib -lR
R里调用还是老办法:
dyn.load("fibWrapper.so")
.Call("fibWrapper",10)
## [1] 55
Rcpp的优势
Rcpp可以被成为“无缝链接”的原因,我个人认为就两个,一个是省去了内存管理的麻烦,一个是inline方法。
内存管理
当初最早看到Rcpp的时候,Dirk也是用的这个例子。从这点来看,他真的不会忽悠。这个例子完全没有突出Rcpp的优势,因为用R自带的C语言API,写出来也基本就这个样子。
从我个人角度来看,Rcpp最大的创举在于解决了内存管理问题。如果各位自己用C写过R package,或者看过一些package甚至R语言的源代码,PROTECTED和UNPROTECTED两个宏应该会经常见到的。由于R系统对内存 的管理,如果分配的内存不被保护起来,很有可能会被回收掉。R和C直接进行数据传输,必须使用指针,无论是SEXP还是直接声明的指针。在进行计算时,如 果自己分配了内存空间,也必须进行保护。下面贴了个对比的例子,大家看看就明白了。
//R自带的C API版本,注意PROTECTED和UNPROTECTED
#include
#include
extern "C"SEXP vectorfoo(SEXP a, SEXP b){
int i, n;
double *xa, *xb, *xab; SEXP ab;
PROTECT(a = AS_NUMERIC(a));
PROTECT(b = AS_NUMERIC(b));
n = LENGTH(a);
PROTECT(ab = NEW_NUMERIC(n));
xa=NUMERIC_POINTER(a); xb=NUMERIC_POINTER(b);
xab = NUMERIC_POINTER(ab);
double x = 0.0, y = 0.0 ;
for (i=0; i<n; code="" <="" }="" return(ab);="" unprotect(3);="" -(y*y);="" x*x="" ?="" y)="" res[i]="(x" y="xb[i];" x="xa[i];" {="" i++)="" i
//Rcpp版本 #include SEXP foo( SEXP xs, SEXP ys ){ Rcpp::NumericVector xx(xs), yy(ys) ; int n = xx.size() ; Rcpp::NumericVector res( n ) ; double x = 0.0, y = 0.0 ; for (int i=0; i<n; res="" code="" <="" }="" -(y*y);="" x*x="" ?="" y)="" res[i]="(x" y="yy[i];" x="xx[i];" {="" i++)="" ;="" return="" style="word-wrap: break-word;">
``
这多少是个有点繁琐的事情,而Rcpp自动完成了这一步,也就是说,使用了Rcpp,用户不需要再考虑内存的问题,只要通过Rcpp进行数据转换就 足够了。可以说,现有的C++代码,不需要进行多少修改,就可以被R调用,这也是我认为Rcpp是个里程碑级别成就的主要原因。
inline
虽然说,Rcpp已经足够方便了,但还是要写一个C++文件,编译之后再进行调用,而面对比较简短的C++代码时,这多少有点小题大作了,所以就有了inline。这里的inline和C里面的“内联函数”很类似,还是看Fibonacci数列的例子。
library(inline)
fib <- cxxfunction(signature(xs="int"),
plugin="Rcpp",
body='
int n = Rcpp::as(xs);
int first = 0;
int second = 1;
int third = 0;
for(){
third = first + second;
first = second;
second = third;
}
return first;
')
这样我们就定义了一个inline函数,在R里运行这段代码就足够了,而编译调用等事情,完全被封装了起来。所谓“无缝”,也就如此吧。
最后
这里只聊了Rcpp最简单的一个方面,而且很多东西都略过,比如inline里的plugin等。只是希望大家能对Rcpp的优势有一个直接的认识,更多的细节,大家有兴趣还是去看Dirk的书吧。
还没有评论,来说两句吧...