Rcpp扩展包可以很容易地将C++代码连接到R程序中,并且支持在C++中使用类似于R的数据类型。Rcpp包提供了C++类,这些类极大地促进了使用R提供的.Call接口在R包中连接C或C++代码。Rcpp可以很容易地把C++代码与R程序连接在一起,可以从R中直接调用C++代码而不需要用户关心那些繁琐的编译、链接、接口问题。可以在R数据类型和C++数据类型之间容易地转换。Rcpp支持把C++代码写在R源程序文件内, 执行时自动编译连接调用;也支持把C++代码保存在单独的源文件中, 执行R程序时自动编译连接调用;对较复杂的问题, 应制作R扩展包, 利用构建R扩展包的方法实现C++代码的编译连接,这时接口部分也可以借助Rcpp属性功能或模块功能完成。
1. R程序与由Rcpp支持的C++程序之间的数据转换
1.1. R类型转C++类型
template <typename T> T as(SEXP x);
1.2. C++类型转R类型
template <typename T> SEXP wrap(const T& object);
1.3 R类型转C++类型
不需要显示调用as()和wrap()
2. Rccp包中常见的数据类型及创建
2.1 Vector
NumericVector V1(n);//创立了一个长度为n的默认初始化的数值型向量V1。
NumericVector V2=NumericVector::create(1, 2, 3); //创立了一个数值型向量V2,并初始化使其含有三个数1,2,3。
LogicalVector V3=LogicalVector::create(true,false,R_NaN);//创立了一个逻辑型变量V3。如果将其转化为R Object,则其含有三个值TRUE, FALSE, NA。
2.2 Matrix
NumericMatrix M1(nrow,ncol);//创立了一个nrow*ncol的默认初始化的数值型矩阵。
2.3 Multidimensional Array
NumericVector out=NumericVector(Dimension(2,2,3));//创立了一个多维数组。
2.4 List
NumericMatrix y1(2,2);
NumericVector y2(5);
List L=List::create(Named("y1")=y1,
Named("y2")=y2);
2.6 DataFrame
NumericVector a=NumericVector::create(1,2,3);
CharacterVector b=CharacterVector::create("a","b","c");
std::vector<std::string> c(3);
c[0]="A";c[1]="B";c[2]="C";
DataFrame DF=DataFrame::create(Named("col1")=a,
Named("col2")=b,
Named("col3")=c);
3. R代码与Rcpp包支持的C++代码性能比较
#### C++ 代码可以解决的典型瓶颈:
#### 1. 不容易向量化的循环,后面的迭代依赖前面的结果
#### 2. 递归函数
#### 3.需要R中没有的高级数据结构和算法
testRcpp.cpp代码
#include <Rcpp.h>
using namespace Rcpp;
// [[Rcpp::export]]
double newsum(NumericVector x) {
double total = 0;
NumericVector::iterator it;
for(it = x.begin(); it != x.end(); ++it) {
total += *it;
}
return total;
}
// [[Rcpp::export]]
int fibonacci(int x)
{
if (x == 1 || x == 2) return 1;
return fibonacci(x - 1) + fibonacci(x - 2);
}
// [[Rcpp::export]]
NumericMatrix gibbs_cpp(int N,int thin){
NumericMatrix mat(N,2);
double x = 0, y = 0;
for (int i = 0; i < N; i++){
for (int j = 0; j < thin; j++){
x = rgamma(1, 3, 1 / (y * y + 4))[0];
y = rnorm(1, 1 / (x + 1), 1 / sqrt(2 * (x + 1)))[0];
}
mat(i,0) = x;
mat(i,1) = y;
}
return (mat);
}
## 3.1 向量求和
aa <- sample(0:1000,1000000, replace = TRUE) # 10到1000之间有放回随机抽样10000个
# aa <- runif(10000,10,100) # 10 到100之间均匀分布的10000个数
start <- Sys.time()
#aa_sum <- newsum(aa)
aa_sum <- sum(aa)
end <- Sys.time()
duration <- end - start
duration
## R语言代码运行的更快!
## 3.2. 求斐波那契数列的第 x 项的函数。
# R语言函数
fibonacci_r <- function(n){
if (n == 1 || n == 2)
return (1)
return (fibonacci_r(n - 1) + fibonacci_r(n - 2))
}
start <- Sys.time()
#result <- fibonacci(30) # 调用C++代码
result <- fibonacci_r(30) # R函数代码
end <- Sys.time()
print (c(paste0("运行时间:", end-start)))
### 0.0083 Vs 0.6789
## 3.3 gibbs采样
gibbs_r <- function(N, thin){
mat <- matrix(nrow = N, ncol = 2)
x <- y <- 0
for (i in 1:N){
for (j in 1:thin){
x = rgamma(1, 3, y * y + 4)
y = rnorm(1, 1 / (x + 1), 1 / sqrt(2 * (x + 1)))
}
mat[i,] = c(x,y)
}
mat
}
## 注意:R代码和C++代码语法的差异:语句结尾,变量,循环,矩阵取赋值等
# install.packages("microbenchmark")
library(microbenchmark)
microbenchmark(gibbs_r(100,10),gibbs_cpp(100,10))
4. Rcpp属性
Rcpp属性的主要组成部分如下:
- 在C++中,提供Rcpp::export标注要输出到R中的C++函数。
- 在R中,提供sourceCpp(), 用来自动编译连接保存在文件或R字符串中的C++代码, 并自动生成界面程序把C++函数转换为R函数。
- 在R中,提供cppFunction()函数, 用来把保存在R字符串中的C++函数自动编译连接并转换成R函数。 提供evalCpp()函数, 用来把保存在R字符串中的C++代码片段自动编译连接并执行。
- 在C++中,提供Rcpp::depends标注, 为了sourceCpp()说明编译连接C++代码时需要的外部头文件和库的位置。
- 在构建R扩展包时,提供compileAttributes() R函数, 自动给C++函数生成相应的 extern C声明和.Call接口代码。
5. Rcpp糖(sugar)
在C++中,向量和矩阵的运算通常需要逐个元素进行, 或者调用相应的函数。
Rcpp通过C++的表达式模板(expression template)功能和懒惰求值(lazy evaluation)功能,
可以在C++中写出像R中对向量和矩阵运算那样的向量化表达式。 这称为Rcpp糖(sugar)。
参考:
https://www.math.pku.edu.cn/teachers/lidf/docs/Rbook/html/_Rbook/rcpp.html