Made with OpenOffice.org 1 R と C/C++ の連携 福島真太朗 第 7 回 R 勉強会@東京 2010 年 7 月 24 日
Made with OpenOffice.org 2
出身:埼玉県職業:コンサルタント 主に金融工学のデータ解析・モデル構築等使用言語:R, C, C++, Java, Awk, PerlTwitter: @sfchaos
自己紹介
Made with OpenOffice.org 4
1.本発表の目標
R C/C++
Rでは遅い処理をC/C++に任せて高速化したい
蓄積してきたC/C++のライブラリを
活用したい
Rと C/C++の連携させる基本的な方法を学ぶ
将来こんな問題に直面するかも・・・
Made with OpenOffice.org 5
2.Rからの C/C++の利用
Rから C/C++を利用する方法として, 主に2種類の方法が用いられる.
.C()関数を利用する方法
.Call()関数を利用する方法
Made with OpenOffice.org 7
2.1 .C()関数を利用する方法
Rオブジェクト
Rオブジェクト
R C/C++共用
オブジェクト( または DLL)
C/C++コードの作成
SO(/DLL)の作成
SO(/DLL)のRへの読み込み
Rの SO呼び出し関数の作成
Rでの関数呼び出し
.C()関数
Made with OpenOffice.org 8
1 extern "C" void convolve_c(double *a, int *na, double *b, int *nb, double *ab) 2 { 3 int nab = *na + *nb -1; 4 5 for (int i = 0; i < nab; ++i) ab[i] = .0; 6 for (int i = 0; i < *na; ++i) { 7 for (int j = 0; j < *nb; ++j) { 8 ab[i+j] += a[i] * b[j]; 9 }10 }11 }
引数はポインタ渡し(最後の引数がRに渡す値を 格納する配列の先頭ポインタ)
C++コードでは“extern C”をつける
関数の返り値は void型
Step1) C/C++コードの作成
Made with OpenOffice.org 9
> dyn.load("conv_c.so")
convolve.r <- convolve.r <- function(a, b) .C("convolve", function(a, b) .C("convolve", as.double(a), as.integer(length(a)),as.double(a), as.integer(length(a)), as.double(b), as.integer(length(b)), as.double(b), as.integer(length(b)), res = double(length(a)+length(b)-1)) res = double(length(a)+length(b)-1))
[sfukushima@localhost test]$ R CMD SHLIB conv_c.cpp
conv.c <- function(a, b) .C("conv_c", as.double(a), as.integer(length(a)), as.double(b), as.integer(length(b)), res = double(length(a)+length(b)-1))
Step3) SO(/DLL)の Rへの読み込み
Step2) SO(/DLL)の作成
Step4) Rの SO(/DLL)呼び出し関数の作成・第1引数は,C/C++の関数名・引数の型は厳密に指定・最後の引数に返り値の型と サイズを指定
Made with OpenOffice.org 10
> conv.c(1:10, 10:1)[[1]] [1] 1 2 3 4 5 6 7 8 9 10
[[2]][1] 10
[[3]] [1] 10 9 8 7 6 5 4 3 2 1
[[4]][1] 10
$result [1] 10 29 56 90 130 175 224 276 330 385 330 276 224 175 130 90 56 29 10
Step5) Rでの関数呼び出し
Made with OpenOffice.org 11
2.2 .Call()関数を利用する方法
Rオブジェクト
Rオブジェクト
R C/C++共用
オブジェクト( または DLL)
C/C++コードの作成
SO(/DLL)の作成
SO(/DLL)のRへの読み込み
Rの SO呼び出し関数の作成
Rでの関数呼び出し
.Call()関数
Made with OpenOffice.org 12
1: extern "C" void convolve_c(double *a, int *na, 1: extern "C" void convolve_c(double *a, int *na, double *b, int *nb, double *ab) double *b, int *nb, double *ab) 2: {2: { 3: 3: int nab = *na + *nb -1; int nab = *na + *nb -1; 4: 4: 5: 5: for (int i = 0; i < nab; ++i) ab[i] = .0; for (int i = 0; i < nab; ++i) ab[i] = .0; 6: 6: for (int i = 0; i < *na; ++i) { for (int i = 0; i < *na; ++i) { 7: for (int j = 0; j < *nb; ++j) ab[i+j] += a[i] * b[j];7: for (int j = 0; j < *nb; ++j) ab[i+j] += a[i] * b[j]; 8: 8: } } 9: }9: }
1 #include <Rdefines.h> 2 3 SEXP convolve_call(SEXP a, SEXP b) 4 { 5 PROTECT(a = AS_NUMERIC(a)); 6 PROTECT(b = AS_NUMERIC(b)); 7 int na = length(a), nb = length(b), nab = na + nb -1; 8 SEXP ab; 9 PROTECT(ab = NEW_NUMERIC(nab));1011 for (int i = 0; i < nab; ++i) REAL(ab)[i] = .0;1213 for (int i = 0; i < na; ++i) {14 for (int j = 0; j < nb; ++j) {15 REAL(ab)[i+j] += REAL(a)[i] * REAL(b)[j];16 }17 }18 UNPROTECT(3);19 return(ab);20 }
引数,返り値ともに型は”SEXP”
(Symbolic EXPresion)
SEXP型は Rがメモリを開放しないように
制御する
Rがメモリを開放できるようにする
Step1) C/C++コードの作成
変数の型を陽に指定する
Made with OpenOffice.org 13
> dyn.load("conv_call.so")
conv.call <- function(a, b) .Call("conv_call", a, b)
[sfukushima@localhost test]$ R CMD SHLIB conv_call.cpp
convolve.r <- convolve.r <- function(a, b) .C("convolve", function(a, b) .C("convolve", as.double(a), as.integer(length(a)),as.double(a), as.integer(length(a)), as.double(b), as.integer(length(b)), as.double(b), as.integer(length(b)), res = double(length(a)+length(b)-1)) res = double(length(a)+length(b)-1))
> conv.call(1:10, 10:1) [1] 10 29 56 90 130 175 224 276 330 385 330 276 224 175 130 90 56 29 10
Step2) SO(/DLL)の作成
Step3) SO(/DLL)の Rへの読み込み
Step4) Rの SO(/DLL)呼び出し関数の作成
Step5) Rでの関数呼び出し
・第1引数は,C/C++の関数名・引数の型はしてしなくて良い!
Made with OpenOffice.org 14
2.3 これまでのまとめ
.Call()関数はRから呼び出すときに、引数の型を明示的に指定しなくても良い→以降で述べるRcppパッケージは,この特徴をより良く活かしたパッケージ
C/C++コードの作成 引数の渡し方 考えられる活用シーン
.C()関数 ○ △(型を明示 )
既存のライブラリを用いるとき
.Call()関数△
(Rとの連携専用に作成)
○短いC/C++のインタフェースを書いて呼び出すとき
Made with OpenOffice.org 15
2.4 Rcppパッケージ
C/C++コードの作成
SO(/DLL)の作成
SO(/DLL)のRへの読み込み
Rの SO呼び出し関数の作成
Rでの関数呼び出し
Rcppパッケージ
.Call()関数を用いて,C/C++を呼び出す場合のC/C++コードをもっと楽に書けないか?
Rオブジェクト
Rオブジェクト
R C/C++共用
オブジェクト( または DLL).Call()関数
Made with OpenOffice.org 16
1 #include <R.h> 2 #include <Rinternals.h> 3 #include "Rcpp.h" 4 5 RcppExport SEXP conv_rcpp(SEXP a, SEXP b) 6 { 7 RcppVector<double> aa(a); 8 RcppVector<double> bb(b); 9 int nab = aa.size() + bb.size() - 1;1011 Rcpp::NumericVector ab(nab);12 for (int i = 0; i < nab; ++i) ab(i) = .0;13 for (int i = 0; i < aa.size(); ++i) {14 for (int j = 0; j < bb.size(); ++j) {15 ab(i + j) += aa(i) * bb(j);16 }17 }18 RcppResultSet rs;19 rs.add("ab", ab);20 return rs.getReturnList();21 }
#include <Rdefines.h> SEXP convolve_call(SEXP a, SEXP b) { PROTECT(a = AS_NUMERIC(a)); PROTECT(b = AS_NUMERIC(b)); int na = length(a), nb = length(b), nab = na + nb -1; SEXP ab; PROTECT(ab = NEW_NUMERIC(nab));
for (int i = 0; i < nab; ++i) REAL(ab)[i] = .0;
for (int i = 0; i < na; ++i) { for (int j = 0; j < nb; ++j) { REAL(ab)[i+j] += REAL(a)[i] * REAL(b)[j]; } } UNPROTECT(3); return(ab);}
Rcpp使用 Rcpp不使用
Made with OpenOffice.org 17
1 #include <R.h> 2 #include <Rinternals.h> 3 #include <Rdefines.h> 4 #include <Rcpp.h> 5 #include <vector> 6 7 extern "C" SEXP rcpp_test(SEXP x, SEXP y) { 8 std::vector<double> xx(x), yy(y); 9 int n = xx.size();10 std::vector<double> res(n);1112 for (int i = 0; i < n; ++i) {13 double x = xx[i], y = yy[i];14 if (x < y) res[i] = x * x;15 else res[i] = -y * y;16 }17 return Rcpp::wrap(res);18 }
C++内部ではSTLの vectorなどを用いて最後にRcppの wrap関数で SEXPに変換することも可能
Made with OpenOffice.org 18
C/C++コードで使用できるRの APIがある(“R.h”等を使用. “Writing R Extensions”Chapter.6参照 ).C++からの Rの利用方法として,今回はRInsideパッケージを紹介する.RInsideを用いると,C++から Rを呼び出して処理を行わせ,その結果をC++に戻すことが楽になる.
1 #include <iostream> 2 #include <algorithm> 3 #include "Rinside.h" 4 5 int main(int argc, char *argv[]) 6 { 7 const int dim = 10; 8 9 // 線形回帰分析に用いるデータの生成10 std::vector<double> x, y;11 for (int i = 0; i < dim;++i) {12 x.push_back(i); y.push_back(2*i);13 }14 // C++側でのデータの確認15 std::cout << "In c++: x" << std::endl;16 std::copy(x.begin(), x.end(),17 std::ostream_iterator<double>(std::cout, " "));18 std::cout << std::endl;19 std::cout << "In c++: v" << std::endl;20 std::copy(y.begin(), y.end(),21 std::ostream_iterator<double>(std::cout, " "));22 std::cout << std::endl;
となるベクトル をC++で生成
3. C/C++からの Rの利用
R C/C++
処理の呼び出し
計算結果の受け渡し
RInsideパッケージ
Made with OpenOffice.org 19
1 #include <iostream> 2 #include <algorithm> 3 #include "Rinside.h" 4 5 int main(int argc, char *argv[]) 6 { 7 const int dim = 10; 8 9 // 線形回帰分析に用いるデータの生成10 std::vector<double> x, y;11 for (int i = 0; i < dim;++i) {12 x.push_back(i); y.push_back(2*i);13 }14 // C++側でのデータの確認15 std::cout << "In c++: x" << std::endl;16 std::copy(x.begin(), x.end(),17 std::ostream_iterator<double>(std::cout, " "));18 std::cout << std::endl;19 std::cout << "In c++: v" << std::endl;20 std::copy(y.begin(), y.end(),21 std::ostream_iterator<double>(std::cout, " "));22 std::cout << std::endl;
となるベクトル をC++で生成
3 C/C++からの Rの利用
Made with OpenOffice.org 20
#include <iostream>#include <iostream>#include <algorithm>#include <algorithm>#include "RInside.h"#include "RInside.h"
int main(int argc, char *argv[])int main(int argc, char *argv[]){{ const int dim = 10;const int dim = 10;
// // 線形回帰分析に用いるデータの生成線形回帰分析に用いるデータの生成 std::vector<double> x, y;std::vector<double> x, y; for (int i = 0; i < dim;++i) {for (int i = 0; i < dim;++i) { x.push_back(i); y.push_back(2*i);x.push_back(i); y.push_back(2*i); }} // C++// C++ 側でのデータの確認側でのデータの確認 std::cout << "In c++: x" << std::endl;std::cout << "In c++: x" << std::endl; std::copy(x.begin(), x.end(),std::copy(x.begin(), x.end(), std::ostream_iterator<double>(std::cout, " "));std::ostream_iterator<double>(std::cout, " ")); std::cout << std::endl;std::cout << std::endl; std::cout << "In c++: v" << std::endl;std::cout << "In c++: v" << std::endl; std::copy(y.begin(), y.end(),std::copy(y.begin(), y.end(), std::ostream_iterator<double>(std::cout, " "));std::ostream_iterator<double>(std::cout, " ")); std::cout << std::endl;std::cout << std::endl;
23 // R側の分析データオブジェクトの生成24 RInside R(argc, argv);25 R.assign(x, "x"); R.assign(y, "y");26 // Rのコマンドの生成,実行27 std::string evalstr = "cat('In R: x \n'); print(x); \28 cat('In R: y \n'); print(y); \29 z <- lm(y~x); z <- z$fitted.values; \30 cat('In R: fitted.values \n'); print(z); z";31 SEXP ans;32 R.parseEval(evalstr, ans);3334 // C++においてRの返り値の格納35 RcppVector<double> vec(ans);36 std::vector<double> v = vec.stlVector();37 // 確認38 std::cout << "In c++: v" << std::endl;39 std::copy(v.begin(), v.end(),40 std::ostream_iterator<double>(std::cout, " "));41 std::cout << std::endl;
Rでもベクトル を生成
Rのコマンドの生成・受け渡し・実行
Made with OpenOffice.org 21
[sfukushima@localhost test]$ ./rinside_testIn c++: x0 1 2 3 4 5 6 7 8 9 In c++: v0 2 4 6 8 10 12 14 16 18 In R: x [1] 0 1 2 3 4 5 6 7 8 9In R: y [1] 0 2 4 6 8 10 12 14 16 18In R: fitted.values 1 2 3 4 5 6 7 8 9 10 0 2 4 6 8 10 12 14 16 18 In c++: v5.2423e-16 2 4 6 8 10 12 14 16 18
C++でのベクトル の中身の確認
Rでのベクトル の中身の確認
Rの返り値の確認
C++においてRの返り値の確認
Made with OpenOffice.org 22
参考文献
Dirk Eddelbuettel(2009):“Rcpp and Rinside: Easier R and C++ integration”, UseR! 2009 Presentation.
Dirk Eddelbuettel,Romain francois(2010):“Rcpp:Seamless R and C++”.
R Development Core Team(2010): “Writing R Extensions(ver.2.11.0)”.
John M.Chambers(2008): “Software for Data Analysis -Programming with R-”, Springer.