- Rcppを使ってみた(こちら)
- おおまかに分かったので、じゃあ、Rcpp/Cppの強みについておさらいしておこう
- こちらを読む
- Rcppをいつ使う?
- Rのコードでできるだけのことをしても「遅い」とき
- それはいつ?
- ループをRが得意なベクトル化で高速処理できないとき(前の結果に依存するループとか)
- 再帰関数や、関数呼び出しが繰り返しなされるとき(関数呼び出しのオーバーヘッドはC++ << R)
- データ構造としてC++では使いやすくて、Rでは使いにくいものを扱うとき(standard template library (STL)というのは、C++が持つデータ構造ライブラリ→参考1,参考2,参考3)
- Rcppパッケージは、(主に)2つの方法でC++関数をコンパイルしてRから呼び出して使えるようにする
- 短いならcppFunction()で可、長ければsourceCpp()を使うのが普通だろう。どちらか片方を覚えるならsourceCpp()だろう。
- (1) cppFunction()関数を使う
- C++関数を文字列としてcppFunction()に渡す
- (2) sourceCpp()関数を使う
- C++関数をファイルで書いてsourceCpp()関数に読み込ませる
- 2整数の積を返す関数
rprod <- function(x,y){
x*y
}
x <- 3
y <- 2
rprod(x,y)
> rprod <- function(x,y){
+ x*y
+ }
> x <- 3
> y <- 2
> rprod(x,y)
[1] 6
prodstring <- 'int add(int x, int y) {
int pr = x * y;
return pr;
}'
cppFprod <- cppFunction(prodstring)
cppFprod
cppFprod(x,y)
> prodstring <- 'int add(int x, int y) {
+ int pr = x * y;
+ return pr;
+ }'
> cppFprod <- cppFunction(prodstring)
> cppFprod
function (x, y)
.Primitive(".Call")(<pointer: 0x0000000064541770>, x, y)
> cppFprod(x,y)
[1] 6
-
-
- sourceCpp()を使う
- cpp関数のファイルをprcpp.cppとして作成保存する
#include <Rcpp.h>
using namespace Rcpp;
int prcpp(int x, int y) {
int pr = x * y;
return pr;
}
sourceCpp("prcpp.cpp")
prcpp
prcpp(x,y)
> sourceCpp("prcpp.cpp")
> prcpp
function (x, y)
.Primitive(".Call")(<pointer: 0x00000000629c1770>, x, y)
> prcpp(x,y)
[1] 6
rprod <- function(x,y){
x*y
}
int prcpp(int x, int y) {
int pr = x * y;
return pr;
}
-
- Rでは関数を作成する関数function()を使うが、C++では使わない
- Rではreturn()を使っても使わなくてもよいが、C++では必ず使う
- 行末記号をRは使わないがC++は使う
- 型宣言するところが違う
- 関数prcpp()の帰り値がintであることを"int prcpp(..."のように宣言
- 引数がintであると宣言(int x, int yのようにx,yがintであると)
- 関数内部でも新出の変数の型を宣言 int pr ...
- これはC++仕様の基本なのでどこまでもついて回る
- 速さ比べ
- 格子酔歩1
- 原点から出発して二次元格子の4方向を等確率で選んで動く酔歩、n歩分のシミュレーション
- Rの場合
n <- 10000
rRW <- function(n){
stepx <- sample(c(-1,1),n,replace=TRUE)
stepy <- sample(c(-1,1),n,replace=TRUE)
xORy <- sample(c(0,1),n,replace=TRUE)
xselect <- which(xORy==0)
yselect <- which(xORy==1)
x <- y <- rep(0,n)
x[xselect] <- stepx[xselect]
y[yselect] <- stepy[yselect]
x <- c(0,x)
y <- c(0,y)
return(c(cumsum(x),cumsum(y)))
}
outR <- rRW(n)
plot(matrix(outR,ncol=2),type="l")
#include <Rcpp.h>
using namespace Rcpp;
NumericVector cppRW(int n){
NumericVector ret((n+1)*2);
for(int i=1;i<(n+1);i++){
int tmp = n + i + 1;
ret[i] = ret[i-1];
ret[tmp] = ret[tmp-1];
if(rnorm(1)[0] < 0){
if(rnorm(1)[0] < 0){
ret[i]=ret[i]+1;
}else{
ret[i]=ret[i]-1;
}
}else{
if(rnorm(1)[0] < 0){
ret[tmp]=ret[tmp]+1;
}else{
ret[tmp]=ret[tmp]-1;
}
}
}
return ret;
}
sourceCpp("cppRW.cpp")
n <- 10000
outCpp <- cppRW(n)
plot(matrix(outCpp,ncol=2),type="l")
system.time(cppRW(n*100))
system.time(rRW(n*100))
> system.time(cppRW(n*100))
ユーザ システム 経過
0.31 0.02 0.33
> system.time(rRW(n*100))
ユーザ システム 経過
0.34 0.02 0.36
-
- 酔歩は「過去」に依存して発展するとは言え、4方向のどちらに進むかについて、過去に影響されずに選べるので、ステップ方向の乱択がベクトル化で来ている
- では、Self-avoiding pathのようにステップ方向を決めるときに、過去の経路を確認しないといけなくするとどうなるか、やってみる。ループを途中で切り上げたりとか工夫もできるが、べたにやる。
rRWsa <- function(n){
stepx <- sample(c(-1,1),n,replace=TRUE)
stepy <- sample(c(-1,1),n,replace=TRUE)
xORy <- sample(c(0,1),n,replace=TRUE)
xselect <- which(xORy==0)
yselect <- which(xORy==1)
x <- y <- rep(0,n)
x[xselect] <- stepx[xselect]
y[yselect] <- stepy[yselect]
X <- rep(0,n+1)
Y <- rep(0,n+1)
for(i in 2:(n+1)){
tmpx <- X[i-1]
tmpy <- Y[i-1]
tmpx2 <- tmpx + x[i-1]
tmpy2 <- tmpy + y[i-1]
tmpx3 <- X[1:(i-1)]-tmpx2
tmpy3 <- Y[1:(i-1)]-tmpy2
tmpD <- abs(tmpx3) + abs(tmpy3)
if(min(tmpD)>0){
X[i] <- tmpx2
Y[i] <- tmpy2
}else{
X[i] <- X[i-1]
Y[i] <- Y[i-1]
}
}
return(c(X,Y))
}
n <- 10000
outRsa <- rRWsa(n)
plot(matrix(outRsa,ncol=2),type="l")
#include <Rcpp.h>
using namespace Rcpp;
NumericVector cppRWsa(int n){
NumericVector ret((n+1)*2);
for(int i=1;i<(n+1);i++){
int tmp = n + i + 1;
ret[i] = ret[i-1];
ret[tmp] = ret[tmp-1];
int tmpx = ret[i];
int tmpy = ret[tmp];
if(rnorm(1)[0] < 0){
if(rnorm(1)[0] < 0){
tmpx=tmpx+1;
}else{
tmpx=tmpx-1;
}
}else{
if(rnorm(1)[0] < 0){
tmpy=tmpy+1;
}else{
tmpy=tmpy-1;
}
}
bool ok = TRUE;
for(int j=0;j<i;j++){
if(abs(ret[j]-tmpx)+abs(ret[n+1+j]-tmpy) == 0){
ok = FALSE;
}
}
if(ok){
ret[i] = tmpx;
ret[tmp] = tmpy;
}
}
return ret;
}
sourceCpp("prcpp.cpp")
n <- 100000
system.time(cppRWsa(n))
system.time(rRWsa(n))
> n <- 10000
>
> system.time(cppRWsa(n))
ユーザ システム 経過
0.09 0.00 0.09
> system.time(rRWsa(n))
ユーザ システム 経過
0.92 0.00 0.92
>
> n <- 100000
>
> system.time(cppRWsa(n))
ユーザ システム 経過
10.31 0.00 10.33
> system.time(rRWsa(n))
ユーザ システム 経過
96.39 0.29 97.59
- Listの出し入れ
- ベクトル・行列の出し入れは比較的簡単なはずで、確かにその通り
- Rのリストはいろんなデータ型が入り乱れているので、それに対応してくれるようにRcppは設計してある
double hoge(List mod) {
...
- Rの関数(f)をC++に渡すこともできる
- ここでcpp関数の返り値のデータ型が"RObject"となっている。Rの関数f()がどんなものかわからないので、何にでも対応(Listが一番自由度が高い)するようになっている、ということ(らしい)
RObject hage(Function f) {
- 基本は終わり
- 次の記事は、便利に使う、楽に使う、より上手に使う、ためにsugar,STL,Armadilloなどについて