Rcpp简明指南: R调用C++
张金龙
注: 本文需要读者了解:
(1) R函数的基本知识
(2) 编写R程序包
(3) C++的基础知识, 包括 类, 以及namespace等。
为什么要用R调用C++函数? 原因可能有以下几点:
(1) R开发虽然很快, 涉及到一些循环的时候, 执行效率却常常相当于C++的1/100,甚至更慢
(2) C++中有一些已经开发好的库, 人们不希望再重写一遍R的代码。 而是最好通过编译之后直接调用。
Rcpp程序包为用R调用C++代码提供了方便。
用户直接在R的源代码src文件夹下加入cpp和h文件, rtools即可对C++文件进行编译。 但是为了从R的console能够直接调用cpp写的函数, 还必须对cpp的函数进行修改。
修改需要遵循以下原则:
1 所有从R输入到cpp的对象, 都是SEXP
2 从cpp返回到R的对象也必须是SEXP
3 C++中, 返回值如为数值, 可以用wrap()帮忙, 直接返回为SEXP的对象, 实现R的调用
换句话说, 只有加了SEXP这样一个标签,C++函数在 R和C++中才都能识别。 Rcpp提供了SEXP处理的响应的类, 使得两者之间的数据传输变得容易。
以下是用R调用C++的一个简单例子:
如果在windows中要使用Rcpp, 则需要
1 安装Rcpp程序包,install.packages("Rcpp")
2 安装Rtools, 并配制好启动路径。电脑>属性>高级系统设置>高级>环境变量>系统变量>路径
3 在R console 中运行 library(Rcpp) , 之后调用 Rcpp.package.skeleton函数,
Rcpp.package.skeleton( "test" )
在生成的R包 skeleton中, 可以找到rcpp_hello_world.cpp文件, 可以尝试添加几个函数, 修改成如下样式 (注意以下代码为C++文件,扩展名为cpp)
// C++文件开始
#include "rcpp_hello_world.h"
#include <Rcpp.h>
using namespace Rcpp;
using namespace std;
// 注意 #include <Rcpp.h> 是调用Rcpp的必要条件
RcppExport SEXP intVec1a(SEXP vec) {
Rcpp::NumericVector vec2(vec);
int prod = 1;
for (int i=0; i<vec2.size(); i++) {
prod *= vec2[i];
}
return (wrap(prod));
}
// RcppExport 是为了在载入R包后, 在Namespace内内能够找到这个C++的函数
//第一个SEXP声明返回值的类型
//第二个SEXP, 声明vec变量为SEXP的类型,也就是说, 它可以是从R直接传入
// Rcpp::NumericVector vec2(vec); 意思 是将 vec转换为 vec2, 从SEXP转换为 NumericVector, NumericVector是Rcpp下的一个类, 在C++的函数中可以识别和调用。 后面的代码就和普通的C++没有什么分别了。
//return (wrap(prod)); 由于我们希望在R中直接可以得到该函数的运行结果, 因此, 返回值也需要转换为SEXP类型。 wrap函数提供了一种便捷的方式。 简单的类型, 如int, double等, 只需要直接调用wrap即可。
RcppExport SEXP Eccentricity(SEXP JD)
{
double JD3 = Rcpp::as<double>(JD);
double T = (JD3 - 2451545.0) / 36525.0;
double Tsquared = T*T;
return (wrap(1 - 0.002516*T - 0.0000074*Tsquared));
}
// Rcpp::as<double>(JD); 表示调用Rcpp内的方法, 将类型为SEXP的JD, 转换为double类型, 并赋值給double JD3
RcppExport SEXP expfun(SEXP x){
double x2 = Rcpp::as<double>(x);
double ans=1.0, term=1.0, eps=1e-16;
int n=0;
while (fabs(term)>eps){
n++;
term = term * x2 / n;
ans = ans + term;
}
return(wrap(ans));
}
/ C++文件结束
对R程序包test的骨架进行修改后, 即可开始安装,
在windows下, 在 开始, cmd, 输入 Rcmd INSTALL test, 安装test程序包
之后转入 R console, library(test)
即可加载响应的动态链接库dll文件
只是此时调用dll中的文件, 还稍微有些不方便。 此时调用dll中函数的方法如下
.Call('Eccentricity', JD= 2314543)
.Call('intVec1a', vec= c(3, 4, 5))
.Call('expfun', x=3)
不过每次使用.Call函数, 仍然让人觉得不太满意。为了需要写一个R的wrapper function例如
expfunR <- function(x){
return( .Call('expfun', x= x))
}
EccentricityR <- function(JD){
return( .Call('Eccentricity', JD= JD))
}
intVecR <- function(x){
return(.Call('intVec1a', vec= x))
}
调用方式同R函数,
expfunR(2)
EccentricityR(2314543)
intVecR(c(2,3,4))
这时候就和一般的R函数没有区别了。
(2013.11.18 星期一 哈尔滨初次暴雪)