C++
能解决的瓶颈问题有:
- 由于迭代依赖于之前结果,循环难以简便的向量化运算
- 递归函数,或者是需要对同一个函数运算成千上万次
- R语言缺少一些高级数据结构和算法
我们只需要在代码中写一部分C++
代码来就可以处理上面这些问题。后续操作在Windows下进行,你需要安装Rtools,用install.packages("Rcpp")
安装新版的Rcpp,最重要一点,你需要保证你R语言时不能是C:/Program Files/R/R-3.5.1/
这种形式,否则会报错。
后续操作会用到microbenchmark
包来评估R代码和RCPP的效率差异,用install.packages('microbenchmark)
安装
RCPP入门
先从一个简单的add
函数开始,学习如何用cppFunction
在R里面写C++
代码
1 | library(Rcpp) |
Rcpp将会编译C++
代码, 然后构建能够连接到C++函数的R函数。后续将会介绍如何将一些R代码改写成C++
代码。
- 标量输入,标量输出
- 向量输入,标量输出
- 向量输入,向量输出
- 矩阵输入,向量输出
没有输入,标量输出
最简单的函数就是不提供任何输出,返回一个输出,比如说
1 | one <- function() 1L |
等价的C++
代码是
1 | int one(){ |
那么将这段C++
代码在R用cppFunction
中改写就是如下
1 | cppFunction('int one(){ |
上面这段函数就展示了R和C++
之间一些重要区别:
C++
写代码不是函数名 <- function(参数){}
而是函数名(函数参数){}
C++
中必须声明返回类型,ini
就是标量整数。C++对应R语言常用向量的类是:NumericVector
,IntegerVector
,CharacterVector
和LogicalVector
.- R语言没有标量,全是向量。而
C++
有向量和标量之分,标量的数据类型是double
,int
,String
和bool
C++
你必须要用到return
声明要返回的数据- 每段代码后要跟着
;
标量输入,标量输出
我们可以写一个函数,sign
,他的功能就是把一个负数转成正数,正数不变
1 | signR <- function(x){ |
这个例子中要注意两件事情
- 在
C++
中,你需要声明输入的数据类型 C++
和R的条件语句长得一样。
向量输入,标量输出
R和C++
一大区别就是R的循环效率很低。因此在R语言要尽量避免使用显示的循环语句,尽量向量化运算函数。而C++
的循环花销特别小,所以可以放心大胆的用。
让我们用R代码写一个求和函数sum
以及 C++
的求和函数,然后比较下效率
1 | sumR <- function(x){ |
C++
版本和R版本的逻辑相同,但是有如下不同
- 用
.size()
确认向量的长度 for
的写法为for(初始值; 判断语句; 递增)
- 记住:
C++
的向量索引从0开始,R是从1开始 - 向量赋值是
=
而不是<-
total += x[i]
等价于total = total + x[i]
, 类似的符号还有-=
,*=
,/=
最后用microbenchmark
比较下,R自带求和函数和我们自己写的两个版本的差异
1 | x <- runif(1000) |
最快的是高度优化过的内置函数,最差的就是sumR()
, 速度会比sumC()
慢10倍以上。
向量输入,向量输出
R中比较常见的操作就是向量间运算,尤其R还会自动补齐。自动补齐某些时候会造成一些问题,但是C++不存在这个问题。我们可以写一个RCPP的+
函数
1 | cppFunction('NumericVector addC(NumericVector x, NumericVector y){ |
矩阵输入,向量输出
每个向量类型都有矩阵等价类,NumericMatrix
, IntegerMatirx
, CharacterMatirx
, LogicalMatirx
. 让我们尝试写一个rowSums()
函数
1 | cppFunction('NumericVector rowSumsC(NumericMatrix x){ |
这里注意有两点不同,在C++
中,你用()
对矩阵取值,而不是[]
尽管看起来C++
的代码运行起来比R语言快多了,比如说R要一分钟,RCPP只要一秒,但是如果算上我们写代码的时间和调试代码的时间,刚开始不熟练估计要10分钟,那么总体来看,还是直接上手写R代码比较合适。
但是如果有一些代码要不断复用,那么写C++
代码还是很划算。这个时候就建议将代码写到专门的文本中,用sourceCpp()
加载,而不是cppFunction()
函数
在Rsutdio中可以创建一个C++
模板文件,代码写完之后还可以进行debug。
比如说在里面写上面的rowSumsC
函数,分为如下几个部分
导入头文件,加载Rcpp到命名空间中,类似于library()
1 |
|
使用// [[Rcpp::export]]
说明这里的函数会被R使用
1 | // [[Rcpp::export]] |
下面部分会在sourceCpp()
加载后自动运行
1 | /*** R |
将文件保存成rowSumsC.cpp
, 之后在R里用sourceCpp(file = "rowSumsC.cpp")
。