Rcppには、Rの各種ベクトル・行列に対応するクラスがひと通り用意されている。IntegerVector
NumericMatrix
などだ。一方、Rcppで関数を書くときには標準ライブラリのstd::vectorを使うことも許されている。例えば、std::vector<int>
型の引数を取る関数を定義してそれをRから用いる場合、Rのベクトルオブジェクトが舞台裏でC++の整数ベクトル型に変換されるので、同様の挙動が得られる。
関数内で行列演算などの数値計算をたくさん行う場合には、Rcpp上の型を用いるほうが様々な演算が定義されているので便利だし効率もいい。だけど、そこまでの数値計算を要さない処理である場合、標準ライブラリを使っていた方がC++に慣れていれば書きやすい意味もある。
変数をどう受け渡すのが良いか、変換効率について比較した。結論としては
- 入力変数のサイズがあまり大きくなければ差はそれほどない。
-
IntegerVector
が引数の関数にはRから整数ベクトルを渡すと速い(型変換がないため)が、実数ベクトルを渡すと型変換が起こって遅くなる。 - 同様に
NumericVector
を取る関数にはRから実数ベクトルを渡すと速いが、整数ベクトルを渡すと型変換が起こって遅くなる。 - 標準ライブラリのベクトルを用いると、必ず型変換が起こるので遅い。
library(Rcpp)
library(dplyr)
library(ggplot2)
library(microbenchmark)
cppFunction(
'void TakeRcppIntVector(Rcpp::IntegerVector x)
{
return;
}'
)
cppFunction(
'void TakeStdIntVector(std::vector<int> x)
{
return;
}'
)
cppFunction(
'void TakeRcppRealVector(Rcpp::NumericVector x)
{
return;
}'
)
cppFunction(
'void TakeStdRealVector(std::vector<double> x)
{
return;
}'
)
# performance check
d <- NULL
for (s in 1:6)
{
intvec <- rep(1L, 10^s)
realvec <- rep(1, 10^s)
m <- microbenchmark(TakeRcppIntVector(intvec), TakeStdIntVector(intvec),
TakeRcppIntVector(realvec), TakeStdIntVector(realvec),
TakeRcppRealVector(intvec), TakeStdRealVector(intvec),
TakeRcppRealVector(realvec), TakeStdRealVector(realvec))
cat(sprintf("\n size = 1e+%d\n", s))
print(m)
m <- as.data.frame(m)
m$size <- 10^s
d <- rbind(d, m)
}
v <- group_by(d, size, expr) %>%
summarize(Mean = mean(time), Med = median(time),
LB = quantile(time, 0.1), UB = quantile(time, 0.9))
v$input <- ifelse(grepl("intvec", v$expr), "pass integer", "pass numeric")
v$func <- sub("\\(.*\\)", "", v$expr)
ggplot(v, aes(x = size, y = Med, color = input)) +
geom_point() + geom_line() +
xlab("Vector Size") + ylab("Time (nano sec)") +
geom_ribbon(aes(ymin = LB, ymax = UB, fill = input),
alpha = 0.3, size = 0) +
scale_y_log10() + scale_x_log10() +
facet_grid(~func) +
theme(legend.position="bottom")