84.ADR之Performance code 之 Rcpp
本来打算看完Rcpp包自带的整个文档再写的,但是还是决定先按照ADR中介绍的写一下,以后应该会深入学习Rcpp这个包的(因为我是C++派的),到时候我会抓出其中的精髓介绍给大家的[s:11]
说起这个Rcpp,貌似主要是Dirk Eddelbuettel and Romain Francois这两个人开发的,也还有其他3个重要的份量级人物,我只提前两个人是因为,我这么一个大菜鸟去了rcppmaillist,问了一个特新手的问题,就是怎么在Rcpp的cpp中调用R自带的函数,RF很亲切,给了我调用函数的方法,但是没告诉我结果的类型转换问题,于是我又问了结果类型转换怎么做,结果DE把我说了一通[s:18],大概意思是你怎么啥都不会,其实我是理解DE的,每天上来新手总是问重复的问题,他们这些大神确实会烦,哈哈,这些是题外话,我想展示的是,大家研究Rcpp不懂的地方可以去rcpp邮件列表直接去问作者!
好了,我们来看看Hadley wickham怎么介绍Rcpp包的.
首先,Rcpp可以在几个方面很有用:
1.写loops的时候 因为R的loop中如果有copy on modify的情况就完了,每次循环都要复制一整个向量,所以用C++的loop,针对性的使用很少的临时变量,避免一大段一大段的向量复制
2.递归的时候,因为递归需要频繁调用函数,而C++调用函数开销比R小的多
3.利用C++ STL中重要的数据结构和算法的时候
Rcpp应该是写了很多类来代表R中的结构,最简单的就是
Scalar的部分
The scalar equivalents of numeric, integer, character, and logical vectors are: double, int, String, and bool
R中的scalar其实就是长度为1的vector
Vector的部分
NumericVector, IntegerVector,CharacterVector, and LogicalVector.
比较顾名思义了,对于矩阵的部分;
NumericMatrix, IntegerMatrix, CharacterMatrix, and LogicalMatrix
有了这些类之后,这些类已经写好了很多类方法了,我们就可以调用它们的方法,这些类很多操作符也已经重载好了,给个例子:
cppFunction('NumericVector rowSumsC(NumericMatrix x) {<br />
int nrow = x.nrow(), ncol = x.ncol();<br />
NumericVector out(nrow);<br />
for (int i = 0; i < nrow; i++) {<br />
double total = 0;<br />
for (int j = 0; j < ncol; j++) {<br />
total += x(i, j);<br />
}<br />
out[i] = total;<br />
}<br />
return out;<br />
}')<br />
set.seed(1014)<br />
x <- matrix(sample(100), 10)<br />
rowSums(x)<br />
#> [1] 458 558 488 458 536 537 488 491 508 528<br />
rowSumsC(x)<br />
#> [1] 458 558 488 458 536 537 488 491 508 528
对于这个例子,对于NumericMatrix这个类,.ncol(),.nrow()都是写好的类方法作用是我们熟知的R中的作用,然后还展示了NumericVector的构造函数,其中x(i,j)用来取矩阵的元素,我觉得是重载了()操作符,out
,可以预见NumericVector底层用了数组或者是c++的vector吧
这样一来我们基本了解Rcpp的框架了:对应R中的数据结构的类,类方法对象方法,重载操作符.
我们之前展示的是cppFunction包裹起来,这样不方便,所以我们通常这样写cpp文件;
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
double meanC(NumericVector x) {<br />
int n = x.size();<br />
double total = 0;<br />
for(int i = 0; i < n; ++i) {<br />
total += x[i];<br />
}<br />
return total / n;<br />
}<br />
/*** R<br />
library(microbenchmark)<br />
x <- runif(1e5)<br />
microbenchmark(<br />
mean(x),<br />
meanC(x)<br />
)<br />
*/
注意其中的#include <Rcpp.h>
using namespace Rcpp; 必须得在cpp文件中
// [[Rcpp::export]] 必须用在每个要给用户使用的函数前面
最后特别注意的是/*** R */这个也很有用,我们可以在其中写R的测试代码,然后sourceCpp()的时候这些代码被自动运行然后打印出来,要注意R和***有个空格
下面举个复杂点的例子
int f4(Function pred, List x) {<br />
int n = x.size();<br />
for(int i = 0; i < n; ++i) {<br />
LogicalVector res = pred(x[i]);<br />
if (res[0]) return i + 1;<br />
}<br />
return 0;<br />
}
这个例子大家可以想想对应R中的什么功能
好了,我们来做个练习,实现:
diff(). Start by assuming lag 1, and then generalise for lag n.
下面是我的解答,大家可以给出自己的版本
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
NumericVector diffC(NumericVector x ,int lag=1) {<br />
int n=x.size();<br />
if(lag>n/2) stop("wrong");<br />
if(n>1){<br />
NumericVector out(n-lag);<br />
for(int i=0;i<n-lag;++i) {<br />
out[i]=x[i+lag]-x[i];<br />
}<br />
return out;<br />
} else {<br />
return x;<br />
}<br />
}<br />
接着我们看看attributes怎么用:
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
NumericVector attribs() {<br />
NumericVector out = NumericVector::create(1, 2, 3);<br />
out.names() = CharacterVector::create("a", "b", "c");<br />
out.attr("my-attr") = "my-value";<br />
out.attr("class") = "my-class";<br />
return out;<br />
}
我们可以用属于类的方法create来通过scalar来创建vector.由于R中每个对象都有attributes,我们可以通过.attr()来查询或修改,当然.names()是名字属性的一个别名,然后class也是一种属性,这些基础的知识大家得知道哈
For S4 objects, .slot() plays a similar role to .attr(). 这个我们就不说了[s:11]因为我看不懂
接着我们来看看List和DataFrame怎么用:
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
double mpe(List mod) {<br />
if (!mod.inherits("lm")) stop("Input must be a linear model");<br />
NumericVector resid = as<NumericVector>(mod["residuals"]);<br />
NumericVector fitted = as<NumericVector>(mod["fitted.values"]);<br />
int n = resid.size();<br />
double err = 0;<br />
for(int i = 0; i < n; ++i) {<br />
err += resid[i] / (fitted[i] + resid[i]);<br />
}<br />
return err / n;<br />
}<br />
mod <- lm(mpg ~ wt, data = mtcars)<br />
mpe(mod)<br />
#> [1] -0.0154
很多R返回的结果都是一个list,然后加一些class属性,我们针对这个最通常情况的Rcpp写法就是,参数为List,然后第一个语句就用.inherits()判断是不是设定的某个class,不是就用stop停止,是的话,就针对我们的需要提取list特定的部分然后用as转成我们需要的类型,再分析,注意这里的as是个模板类型
这个例子很好的展示了怎么分析class为lm的对象,这个流程用来分析s3对象是很重要的
接着我们来看看Function怎么用:
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
RObject callWithOne(Function f) {<br />
return f(1);<br />
}<br />
callWithOne(function(x) x + 1)<br />
#> [1] 2<br />
callWithOne(paste)<br />
#> [1] "1"
这个例子展示了最基本的如何从C++调用R的函数,对于按位置传参基本没什么,但是对于命名参数的传参,Rcpp
给了_[""]的写法:
RObject ff(Function f){<br />
NumericVector a=NumericVector::create(1,2,3);<br />
return f(a,_["lag"]=2);<br />
}
如果我们ff(mean)就相当于mean(a,lag=2).
RObject是可以捕捉所有类型,因为我们不知道调用的函数会出现什么样的结果
我们还可以返回一个List
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
List lapply1(List input, Function f) {<br />
int n = input.size();<br />
List out(n);<br />
for(int i = 0; i < n; i++) {<br />
out[i] = f(input[i]);<br />
}<br />
return out;<br />
}
如果看过之前的帖子,或ADR这本书读下来,我们知道这是一个lapply的一个CPP版本
当然我们还有其他类型
There are also classes for many more specialised language objects: Environment, ComplexVector,
RawVector, DottedPair, Language, Promise, Symbol, WeakReference, and so on.
这就需要大家自己去读Rcpp那200多页的pdf了,我还没读呢,我觉得近期是肯定得读的,有可能让我们更了解R的结构
开头我提到了我去rcppmaillist问了一个问题,就是怎么调用R的函数,基本上是这样
??meanC(NumericVector x) {<br />
Function mean=Environment("package::base")["mean"];</p>
<p> return mean(x);<br />
}<br />
这样就是去base包里取出mean然后赋值给Function mean但是这个代码的问题是结果不知道什么类型,mean(x)返回的是SEXP,我让函数返回RObject都出错... 这个问题等我看完Rcpp再来解决吧,但是这个例子展示了如何调用R里面的函数,通过用包名构造evironment对象,然后提取对应的R函数,这个方法很重要,所以我提前展示这个例子
下面,我们来讨论缺失值怎么处理
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
List scalar_missings() {<br />
int int_s = NA_INTEGER;<br />
String chr_s = NA_STRING;<br />
bool lgl_s = NA_LOGICAL;<br />
double num_s = NA_REAL;<br />
return List::create(int_s, chr_s, lgl_s, num_s);<br />
}<br />
str(scalar_missings())<br />
#> List of 4<br />
#> $ : int NA<br />
#> $ : chr NA<br />
#> $ : logi TRUE<br />
#> $ : num NA
我们来一个个分析每个类型的缺失:
对于Integers,缺失值被当作最小的整数储存,R中设定了它们一些行为,而C++不会知道,所以evalCpp('NA_INTEGER + 1') 会给出-2147483647这个结果
所以为了得到正常的效果,我们要创建长度为1的IntegerVector
<br />
IntegerVector a=IntegerVector::create(NA_INTEGER)
这样就可以了
对于Doubles,作者说R中的NA是IEEE浮点数NaN的一种特殊形式(C++中NAN),它的表现如下:
表达式涉及到NAN出现FALSE:
evalCpp("NAN == 1")<br />
#> [1] FALSE
但要注意和逻辑值结合的时候
evalCpp("NAN && TRUE")<br />
#> [1] TRUE<br />
evalCpp("NAN || FALSE")<br />
#> [1] TRUE
最后,在数值的上下文中
evalCpp("NAN + 1")<br />
#> [1] NaN
对于Strings,由于String是Rcpp写的类,所以知道怎么对付缺失值
对于Boolean,C++的bool只有false,true而R是FALSE,TRUE,NA,所以要注意如果一个长度为1逻辑向量中含有缺失值就会被转为TRUE
bool f() {<br />
LogicalVector a=LogicalVector::create(NA_LOGICAL);<br />
return a;<br />
}
最开始的scalar_missings也展示了同样的效果
为了判断一个向量中的一个值是不是缺失,用类方法is_na():
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
LogicalVector is_naC(NumericVector x) {<br />
int n = x.size();<br />
LogicalVector out(n);<br />
for (int i = 0; i < n; ++i) {<br />
out[i] = NumericVector::is_na(x[i]);<br />
}<br />
return out;<br />
}<br />
is_naC(c(NA, 5.4, 3.2, NA))<br />
#> [1] TRUE FALSE FALSE TRUE
或者用一个语法糖
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
LogicalVector is_naC2(NumericVector x) {<br />
return is_na(x);<br />
}<br />
is_naC2(c(NA, 5.4, 3.2, NA))<br />
#> [1] TRUE FALSE FALSE TRUE
接下来,我们就做个练习:
Rewrite cumsum() and diff() so they can handle missing values. Note that these functions have
slightly more complicated behaviour
我们写cumsum:
NumericVector cumsumC(NumericVector x,bool narm=true,bool reserve=true){<br />
LogicalVector pos=!is_na(x);<br />
if(narm){<br />
NumericVector narmx=x[pos];<br />
int n=narmx.size();<br />
NumericVector out(n);<br />
out[0]=narmx[0];<br />
for(int i=1;i<n;++i){<br />
out[i]=out[i-1]+narmx[i];<br />
}<br />
if(reserve){<br />
NumericVector z=clone(x);<br />
z[pos]=out;<br />
return z;<br />
} else {<br />
return out;<br />
}<br />
} else {<br />
if(pos[0]){<br />
int m=0;<br />
int flag=0;<br />
for(int i=0;i<x.size();++i){<br />
if(!pos[i]) {<br />
m=i-1;<br />
flag=1;<br />
break;<br />
}}<br />
if(!flag) m=x.size()-1;<br />
NumericVector y(m+1);<br />
for(int i=0;i<m+1;++i){<br />
y[i]=x[i];<br />
}<br />
NumericVector tmp=cumsumC(y);<br />
NumericVector out(x.size());<br />
for(int i=0;i<x.size();++i){<br />
if(i<=m) out[i]=tmp[i];<br />
else out[i]=NA_REAL;<br />
}<br />
return out;<br />
} else {<br />
NumericVector z=clone(x);<br />
z[pos]=NA_REAL;<br />
return z; }<br />
}<br />
}
这段代码我觉得主要是由于不熟练,所以写的很长,其次是貌似没有一个vector[beg:end]这样的一个重载,所以比较麻烦(当然,也许Rcpp中有介绍,可我目前不知道)
程序的功能就是narm控制去不去除NA,一旦不去除,就只算到第1个非NA的数,这部分用正常的cumsum逻辑,其余为NA,一旦去除,而不保留,就是直接提取非NA数据按正常的cumsum算,结果也直接显示,但是保留的话就是把结果对应到对应位置上去,其余NA还是NA,大家可以自己测试测试
下面来介绍一下Rcpp sugar
主要分为4类
arithmetic and logical operators
logical summary functions
vector views
other useful functions
第一类,arithmetic and logical operators
其实很多基本的算术与逻辑操作符都被向量化了+ *, -, /, pow, <, <=, >, >=, ==, !=, !.
pdistR <- function(x, ys) {<br />
sqrt((x - ys) ^ 2)<br />
}<br />
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
NumericVector pdistC2(double x, NumericVector ys) {<br />
return sqrt(pow((x - ys), 2));<br />
}
这其实就是C++的重载操作符,让这些操作符针对特定类型进行特定行为,这里就是实现了向量化
Logical summary functions
<br />
any_naR <- function(x) any(is.na(x))<br />
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
bool any_naC(NumericVector x) {<br />
return is_true(any(is_na(x)));<br />
}<br />
这里Rcpp的any返回一个可以被is_true,is_false,is_na转化成bool类型的对象
Vector views
head(), tail(), rep_each(), rep_len(), rev(),
seq_along(), and seq_len()
好处就是R的版本会发生很多次复制,Rcpp版本则不会所以效率很高
Other useful functions (我就复制粘贴了啊)
Math functions: abs(), acos(), asin(), atan(), beta(), ceil(), ceiling(), choose(), cos(), cosh(),
digamma(), exp(), expm1(), factorial(), floor(), gamma(), lbeta(), lchoose(), lfactorial(),
lgamma(), log(), log10(), log1p(), pentagamma(), psigamma(), round(), signif(), sin(), sinh(),
sqrt(), tan(), tanh(), tetragamma(), trigamma(), trunc().
Scalar summaries: mean(), min(), max(), sum(), sd(), and (for vectors) var().
Vector summaries: cumsum(), diff(), pmin(), and pmax().
Finding values: match(), self_match(), which_max(), which_min().
Dealing with duplicates: duplicated(), unique().
d/q/p/r for all standard distributions.
Finally, noNA(x) asserts that the vector x does not contain any missing values, and allows optimisation of some mathematical operations.
可以看到,Rcpp sugar用处很大,也可以预见,将来有更多的更方便的Rcpp sugar出现
最后,我们来看看激动人心的STL
我觉得这部分太重要了,特别是对熟悉C++的同学们,作者基本从迭代器的角度出发
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
double sum3(NumericVector x) {<br />
double total = 0;<br />
NumericVector::iterator it;<br />
for(it = x.begin(); it != x.end(); ++it) {<br />
total += *it;<br />
}<br />
return total;<br />
}
这是最基本的例子
然后就是开始使用CPP的资源了
#include <numeric><br />
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
double sum4(NumericVector x) {<br />
return std::accumulate(x.begin(), x.end(), 0.0);<br />
}
这里我们就使用了numeric当中的一个算法,简单的实现了自己的sum,我们已经可以预见接下来要介绍的是有多方便了[s:11]
算法的部分,algorithm含有很多基于迭代器的有用的算法(我还记得TICPP上的那段话,正是有了迭代器才使得算法才能泛型化)
下面的代码展示了使用algorithm的部分算法来实现R中的findInterval
#include <algorithm><br />
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
IntegerVector findInterval2(NumericVector x, NumericVector breaks) {<br />
IntegerVector out(x.size());<br />
NumericVector::iterator it, pos;<br />
IntegerVector::iterator out_it;<br />
for(it = x.begin(), out_it = out.begin(); it != x.end();<br />
++it, ++out_it) {<br />
pos = std::upper_bound(breaks.begin(), breaks.end(), *it);<br />
*out_it = std::distance(breaks.begin(), pos);<br />
}<br />
return out;<br />
}
数据结构的部分:
The STL provides a large set of data structures: array, bitset, list, forward_list, map, multimap,
multiset, priority_queue, queue, dequeue, set, stack, unordered_map, unordered_set,
unordered_multimap, unordered_multiset, and vector</p>
下面我们介绍3种比较常用的:vector, the unordered_set, and the unordered_map.
vectors
我们看看如何用vector来实现rle:
<br />
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
List rleC(NumericVector x) {<br />
std::vector<int> lengths;<br />
std::vector<double> values;<br />
// Initialise first value<br />
int i = 0;<br />
double prev = x[0];<br />
values.push_back(prev);<br />
lengths.push_back(1);<br />
NumericVector::iterator it;<br />
for(it = x.begin() + 1; it != x.end(); ++it) {<br />
if (prev == *it) {<br />
lengths[i]++;<br />
} else {<br />
values.push_back(*it);<br />
lengths.push_back(1);<br />
i++;<br />
prev = *it;<br />
}<br />
}<br />
return List::create(<br />
_["lengths"] = lengths,<br />
_["values"] = values<br />
);<br />
}
具体不作多解释了
然后就是sets的部分,来实现一个duplicated()的功能
// [[Rcpp::plugins(cpp11)]]<br />
#include <Rcpp.h><br />
#include <unordered_set><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
LogicalVector duplicatedC(IntegerVector x) {<br />
std::unordered_set<int> seen;<br />
int n = x.size();<br />
LogicalVector out(n);<br />
for (int i = 0; i < n; ++i) {<br />
out[i] = !seen.insert(x[i]).second;<br />
}<br />
return out;<br />
}
值得注意的是unordered_set在C++ 11中,所以得加个// [[Rcpp::plugins(cpp11)]],然后就是.insert().second这个返回的是插入的值是不是新的,不是新的就相当于重了.first是返回的指向元素的迭代器
最后就是Map来是实现下table
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
std::map<double, int> tableC(NumericVector x) {<br />
std::map<double, int> counts;<br />
int n = x.size();<br />
for (int i = 0; i < n; i++) {<br />
counts[x[i]]++;<br />
}<br />
return counts;<br />
}
我们来做个练习,我们使用CPP的资源来实现which.max
#include <Rcpp.h><br />
#include <algorithm><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
int whichmax(NumericVector x ) {<br />
double val=*std::max_element<NumericVector::iterator>(x.begin(),x.end());<br />
int n=x.size();<br />
for(int i=0;i<n;++i){<br />
if(val==x[i])<br />
return i+1;<br />
}<br />
}<br />
这个是我自己写的,大家可以参考
最后作者给了个case-study:
模拟gibbs sampler
gibbs_r <- function(N, thin) {<br />
mat <- matrix(nrow = N, ncol = 2)<br />
x <- y <- 0<br />
for (i in 1:N) {<br />
for (j in 1:thin) {<br />
x <- rgamma(1, 3, y * y + 4)<br />
y <- rnorm(1, 1 / (x + 1), 1 / sqrt(2 * (x + 1)))<br />
}<br />
mat[i, ] <- c(x, y)<br />
}<br />
mat<br />
}<br />
#include <Rcpp.h><br />
using namespace Rcpp;<br />
// [[Rcpp::export]]<br />
NumericMatrix gibbs_cpp(int N, int thin) {<br />
NumericMatrix mat(N, 2);<br />
double x = 0, y = 0;<br />
for(int i = 0; i < N; i++) {<br />
for(int j = 0; j < thin; j++) {<br />
x = rgamma(1, 3, 1 / (y * y + 4))[0];<br />
y = rnorm(1, 1 / (x + 1), 1 / sqrt(2 * (x + 1)))[0];<br />
}<br />
mat(i, 0) = x;<br />
mat(i, 1) = y;<br />
}<br />
return(mat);<br />
}
对于从R到CPP的版本,这里主要就是Rcpp的矩阵构造以及rgamma等返回一个向量得通过取下标转成一个scalar
然后用矩阵的()操作符
最后作者简单对比了下R的循环版本,向量化版本和C++的循环版本
首先是循环版本
vacc1a <- function(age, female, ily) {<br />
p <- 0.25 + 0.3 * 1 / (1 - exp(0.04 * age)) + 0.1 * ily<br />
p <- p * if (female) 1.25 else 0.75<br />
p <- max(0, p)<br />
p <- min(1, p)<br />
p<br />
}<br />
vacc1 <- function(age, female, ily) {<br />
n <- length(age)<br />
out <- numeric(n)<br />
for (i in seq_len(n)) {<br />
out[i] <- vacc1a(age[i], female[i], ily[i])<br />
}<br />
out<br />
}
其次是向量化版本
vacc2 <- function(age, female, ily) {<br />
p <- 0.25 + 0.3 * 1 / (1 - exp(0.04 * age)) + 0.1 * ily<br />
p <- p * ifelse(female, 1.25, 0.75)<br />
p <- pmax(0, p)<br />
p <- pmin(1, p)<br />
p<br />
}
最后是CPP循环版本
#include <Rcpp.h><br />
using namespace Rcpp;<br />
double vacc3a(double age, bool female, bool ily){<br />
double p = 0.25 + 0.3 * 1 / (1 - exp(0.04 * age)) + 0.1 * ily;<br />
p = p * (female ? 1.25 : 0.75);<br />
p = std::max(p, 0.0);<br />
p = std::min(p, 1.0);<br />
return p;<br />
}<br />
// [[Rcpp::export]]<br />
NumericVector vacc3(NumericVector age, LogicalVector female,<br />
LogicalVector ily) {<br />
int n = age.size();<br />
NumericVector out(n);<br />
for(int i = 0; i < n; ++i) {<br />
out[i] = vacc3a(age[i], female[i], ily[i]);<br />
}<br />
return out;<br />
}
我们来看看benchmark
<br />
n <- 1000<br />
age <- rnorm(n, mean = 50, sd = 10)<br />
female <- sample(c(T, F), n, rep = TRUE)<br />
ily <- sample(c(T, F), n, prob = c(0.8, 0.2), rep = TRUE)<br />
stopifnot(<br />
all.equal(vacc1(age, female, ily), vacc2(age, female, ily)),<br />
all.equal(vacc1(age, female, ily), vacc3(age, female, ily))<br />
)<br />
microbenchmark(<br />
vacc1 = vacc1(age, female, ily),<br />
vacc2 = vacc2(age, female, ily),<br />
vacc3 = vacc3(age, female, ily)<br />
)<br />
#> Unit: microseconds<br />
#> expr min lq median uq max neval<br />
#> vacc1 7,160.0 7,460 7,590.0 7,960.0 11,700.0 100<br />
#> vacc2 352.0 362 404.0 422.0 758.0 100<br />
#> vacc3 54.2 56 63.5 69.3 79.6 100<br />
相信这绝对可以震撼大家,向量化本来就可以有很大提高了,但是CPP还能有10X的提高!
最后的最后,作者写了如何在R包里使用Rcpp,这个以后我会写,目前我还不会写R包更别提看懂这部分,作者也不忘给我们指明了解更多的方向:Rcpp包专门介绍的pdf 以及 C++的经典著作
OK,结束!这几天比较“忙”, 战线拖的有点长[s:11]
</p>