Rcpp的前世今生

拼搏现实的明天。 2022-08-18 11:33 460阅读 0赞

今年六月,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写,很简单的一个实现如下:

  1. fibR<-function(n){
  2. first <- 0
  3. second <- 1
  4. third <- 0
  5. for(i in seq_len(n)){
  6. third <- first + second
  7. first <- second
  8. second <- third
  9. }
  10. return(first)
  11. }

C++的实现如下,其实和普通的C++相比,只是多了一个类型转换而已。其中SEXP是pointer to S expression type,这个是指向R各种类型的一个指针。我不知道各位对“指针”什么感情,反正听到这两个字,如果再涉及内存管理,我就有一个生理性的恐惧,而 Rcpp的伟大之处就在于解决了这个问题,这个后面会提到。

  1. #include
  2. int fib(const int x){
  3. int first = 0;
  4. int second = 1;
  5. int third = 0;
  6. for(int i = 1; i <= x; i++){
  7. third = first + second;
  8. first = second;
  9. second = third;
  10. }
  11. return first;
  12. }
  13. extern "C" SEXP fibWrapper(SEXP xs){
  14. int x = Rcpp::as(xs);
  15. int result = fib(x);
  16. return (Rcpp::wrap(result));
  17. }

编译C++文件从而能够让R调用的方法一般有两种,如果在linux环境下,设置环境变量,命令如下:

  1. $ export PKG_LIBS=`Rscript -e "Rcpp:::LdFlags()"`
  2. $ export PKG_CXXFLAGS=`Rscript -e "Rcpp:::CxxFlags()"`
  3. $ R CMD SHLIB myfile.cpp

还有一种跨平台的方法,windows下只要设置好环境变量也没问题的玩法:

  1. $ Rscript -e "Rcpp:::SHLIB('myfile.cpp')"

其实只要看一下输出结果,就明白了,其实自己手动写完整的命令也不是不行

  1. $ Rscript -e "Rcpp:::SHLIB('fibWrapper.cpp')"
  2. $ 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
  3. $ 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里调用还是老办法:

  1. dyn.load("fibWrapper.so")
  2. .Call("fibWrapper",10)
  3. ## [1] 55

Rcpp的优势

Rcpp可以被成为“无缝链接”的原因,我个人认为就两个,一个是省去了内存管理的麻烦,一个是inline方法。

内存管理

当初最早看到Rcpp的时候,Dirk也是用的这个例子。从这点来看,他真的不会忽悠。这个例子完全没有突出Rcpp的优势,因为用R自带的C语言API,写出来也基本就这个样子。

从我个人角度来看,Rcpp最大的创举在于解决了内存管理问题。如果各位自己用C写过R package,或者看过一些package甚至R语言的源代码,PROTECTED和UNPROTECTED两个宏应该会经常见到的。由于R系统对内存 的管理,如果分配的内存不被保护起来,很有可能会被回收掉。R和C直接进行数据传输,必须使用指针,无论是SEXP还是直接声明的指针。在进行计算时,如 果自己分配了内存空间,也必须进行保护。下面贴了个对比的例子,大家看看就明白了。

  1. //R自带的C API版本,注意PROTECTED和UNPROTECTED
  2. #include
  3. #include
  4. extern "C"SEXP vectorfoo(SEXP a, SEXP b){
  5. int i, n;
  6. double *xa, *xb, *xab; SEXP ab;
  7. PROTECT(a = AS_NUMERIC(a));
  8. PROTECT(b = AS_NUMERIC(b));
  9. n = LENGTH(a);
  10. PROTECT(ab = NEW_NUMERIC(n));
  11. xa=NUMERIC_POINTER(a); xb=NUMERIC_POINTER(b);
  12. xab = NUMERIC_POINTER(ab);
  13. double x = 0.0, y = 0.0 ;
  14. 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
  15. //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数列的例子。

  1. library(inline)
  2. fib <- cxxfunction(signature(xs="int"),
  3. plugin="Rcpp",
  4. body='
  5. int n = Rcpp::as(xs);
  6. int first = 0;
  7. int second = 1;
  8. int third = 0;
  9. for(){
  10. third = first + second;
  11. first = second;
  12. second = third;
  13. }
  14. return first;
  15. ')

这样我们就定义了一个inline函数,在R里运行这段代码就足够了,而编译调用等事情,完全被封装了起来。所谓“无缝”,也就如此吧。

最后

这里只聊了Rcpp最简单的一个方面,而且很多东西都略过,比如inline里的plugin等。只是希望大家能对Rcpp的优势有一个直接的认识,更多的细节,大家有兴趣还是去看Dirk的书吧。

发表评论

表情:
评论列表 (有 0 条评论,460人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Hadoop前世今生

    随着数据的快速增长,数据的存储和分析都变的越来越困难。例如存储容量、读写速度、计算效率等都无法满足用户的需求。为了解决这些问题,Google提出了三个处理大数据的技术手段,分别

    相关 JDBC前世今生

    > “请必须要有自信,你就是一道风景,没必要在别人风景里面仰视。”你好,我是梦阳辰,快来和我一起学习吧! 文章目录 01.JDBC是什么? 02.JDBC快

    相关 ssh前世今生

    1.先说动态网页: 动态网页指那些由网站那边的服务器根据用户的请求动态生成的网页,静态网页与之相反其内容固定不变的。动态网页开发最根本的就是服务器端接收到浏览器提交的请求,通

    相关 Dubbo前世今生

    SOA与服务治理 SOA(面向服务的体系结构)概念由来已久,在10多年前便开始进入到我们广大软件开发者的视线中。SOA是一种粗粒度、松耦合服务架构,服务之间通过简单、精确

    相关 websocke前世今生

    注:下面内容来自网上,本人经过加工整理。 1、问什么要用websocke? Browser已经支持http协议,为什么还要开发一种新的WebSocket协议呢?我们知道ht