Rからc++コードを呼び出すRcpp+inlineを試す

  • ????????????????????

従来、RをCで拡張するときは「Rの基礎とプログラミング技法(→amazon)」にもあるとおり、R.h をインクルードして SEXP (S EXPression) 型と格闘するイメージだけど(何度か実践で使ったけど気楽にやろう、ってものではなかった)、Rcpp + inline を使うと魔法のように簡単に R と c++ を連携させることができるようだ。

なんとインラインで c++ を書いて、それを直接実行する、というすごい実装(inlineパッケージを使った場合。Rcpp は R.h のラッパー)。

使い道としてはMCMCとかやたら遅いアルゴリズムを実装するときに便利そう(C++の中でRの豊富な乱数生成が使えたりするから)。

他にもループが遅い時にループの深いところを c++ で書き換えてしまうとか。かなり気楽だからこそできるワザ。

勉強を兼ねて適当なテスト関数を書いてみた。

インストール

R 2.14.2 + Ubuntu 11.04 にインストール。何やらコンパイルらしきものをしていて多少時間がかかったが問題なくインストール完了。win だとそうもいかないらしい。

install.packages("Rcpp")
install.packages("inline")

インストールしたら読み込み。

library(Rcpp)
library(inline)

c++関数からRのベクトルオブジェクトを返す

基本的なところから。「長さ10の配列を作ってそれらに適当に値を代入して、Rのオブジェクトとして返す」という c++ 関数を作って、それをRの関数として扱う。

cxxfunction の第二引数にコードを文字列で渡して、第三引数におまじないを書くだけ。cxxfunctionを実行した瞬間にg++が動いてコンパイルして、共有ライブラリ的なものをどこかに作っている模様。でもそんなことは全く気にせず、Rの普通の関数と同じように扱えばいい。

c++部分はこんな簡単でいいのか、っていうくらい。NumericVector というクラス (名前空間が省略されているから本当は Rcpp::NumericVector) はほとんど std::vector と同じ扱いでそこにスライスなどの R 特有の操作が追加された感じ。STLに準拠していて非常に素直。

# Dealing with R vector
src <- "NumericVector x(10);
        for(int i=0;i<10;++i)
          x[i]=i;
        return x;"
fun.simple <- cxxfunction(,src,plugin="Rcpp")
fun.simple()

c++の関数にRのオブジェクトを渡す

上の関数は引数をとらなかったけど、もちろん取ることも出来る。Rからc++へは伝統的なSEXP型で渡されるのでそれを Rcpp::NumericVector に変換する必要がある(1行目)。関数のコンパイル時の第一引数に引数の名前とRの型のリストを signature という関数でラップして渡す必要あり。

ちなみにこの例はRのnumeric型(ベクトルのことです)を渡して、偶数番目の要素を0にして返す、という関数になっている。

# Giving args from R to c++, and size of vector
src <- "NumericVector x(xx);
        for(int i=0;i<x.size();++i)
          x[i] = (i%2!=0)?x[i]:0;
        return x;"
fun.make.even.zero <- cxxfunction(signature(xx="numeric"),src,plugin="Rcpp")
fun.make.even.zero(1:10)

STL アルゴリズムの使用

Rcpp::NumericVector は STL コンテナ要件を満たすので STL のアルゴリズムが利用可能。ただし、単純にソートするだけならRの組み込み sort のほうが早かった(1.5~2倍程度)。オーバーヘッドはどこにあるんだろう?共有ライブラリの呼び出しか、SEXP から Rcpp::NumericVector 型への変換部か?

# Using C++/STL algorithm (sort)
src <- "NumericVector x(xx);          // copy
        std::sort(x.begin(),x.end()); // sort
        return x;"
fun.sort <- cxxfunction(signature(xx="numeric"),src,plugin="Rcpp")
system.time(sort(1000000:1))
system.time(fun.sort(1000000:1))  # a bit slower!

c++コードの内部でRの関数を呼び出す

Rの中で呼び出したc++コードの中でさらにRを呼び出すというややこしいワザ。ギブスサンプラーとか実装するときにいいかもしれない。Rcpp::Function 型という便利な型があるのでそこに文字列で突っ込むだけという簡単さ。この例では c++ 関数の中で R の rnorm を呼び出して、引数として平均と標準偏差を名前付きで指定している。名前付きの引数は _[“foobar”] のようにして呼び出す(Named(key,val) のシンタックスシュガーになっている)。

# Calling R functions in c++ cod
src <- "int N = as<int>(n);
        Function rnorm(\"rnorm\");
        return rnorm(N, _[\"mean\"]=5, _[\"sd\"]=3);"  # "Named" arguments
fun.rnorm <- cxxfunction(signature(n="integer"),src,plugin="Rcpp")

プロット

Xが使える環境ならば、c++のコードからRのグラフィック環境を呼び出すという奇妙なこともできる。

# Plotting!!!
src <- "NumericVector _x(x);
        NumericVector _y(y);
        Function plot(\"plot\");
        plot(_x,_y);
        return 0;"
fun.plot <- cxxfunction(signature(x="numeric",y="numeric"),src,plugin="Rcpp")

行列の扱い (1)

ベクトルとだいたい同じなので特に問題なし。さっきも出てきたテンプレート関数 as は SEXP 型を適切に c++ の型に変更する。下の例には書いていないけど、クラスメソッド nrow(), ncol() で行と列の次元を取得可能。

# Dealing with matrix (1) --- building matrix
src <- "int _m=as<int>(m);
        int _n=as<int>(n);
        NumericMatrix A(_m,_n);
        for(int i=0;i<((_m<_n)?_m:_n);++i)
          A(i,i) = 1;
        return A;"
fun.mndiag <- cxxfunction(signature(m="integer",n="integer"),src,plugin="Rcpp")
fun.mndiag(5,3);

行列の扱い (2)

R で行列の行を取り出すときは A[1,] とやるけど、c++ ではそういうわけには行かないので A(1,_) とすることでスライスできる。

# Dealing with matrix (2) --- slicing
src <- "NumericMatrix A(3,3);
        A(2,2)=1;
        NumericVector x = A(_,2);   // this code is equivalent to x <- A[,3] in R
        return x;"
fun.slicer <- cxxfunction(,src,plugin="Rcpp")
fun.slicer()

リストの扱い

List::create でRのリストが作れる。リストの要素名は _[“x”]=(なにかRcpp::NumericVector型など) のように指定すればよい。

# Dealing with list
src <- "Function rnorm(\"rnorm\");
        List X = List::create(_[\"x\"]=rnorm(10), _[\"y\"]=rnorm(10));
        return X;"
fun.list <- cxxfunction(,src,plugin="Rcpp")
fun.list()

データフレームの扱い

データフレームはRでは内部的にはlistなのでRcppでもほとんど同じ扱い。ソースコード読んでないけどRcpp::DataFrameクラスはRcpp::Listクラスを継承しているのだと思う。

# Dealing with data.frame
src <- "Function rnorm(\"rnorm\");
        Function mean(\"mean\");
        DataFrame X = DataFrame::create(_[\"x\"]=rnorm(10), _[\"y\"]=rnorm(10));
        return X;"
fun.data.frame <- cxxfunction(,src,plugin="Rcpp")
fun.data.frame()

boostとか読み込んだりしてみる

これは cxxfunction の include 引数に書き込む。下の例では boost/unordered_map.hpp を読み込んで, 辞書を作っている。

# Calling boost header files and dealing with string
src <- "boost::unordered_map< std::string, std::string > dict;
        std::string _key = as<std::string>(key);
        dict[\"blue\"] = \"sky\";
        dict[\"green\"] = \"leaf\";
        dict[\"red\"] = \"apple\";
        return wrap(dict[_key]);"
fun.dict <- cxxfunction(signature(key="character"),src,
                        plugin="Rcpp",includes="#include <boost/unordered_map.hpp>")
                          # By "includes" argument, preprocessor or macro can be defined.
fun.dict("red")
fun.dict("blue")
fun.dict("green")
fun.dict("pink")

共有ライブラリの読み込み

不明。インラインでは無理かな?

# Linking shared library
#  Unknown... Impossible in inline code??

感想

とにかくすごい。MCMCとか書くときに使ってみたい。RcppEigen というのもかなり気になる。

はてなブックマーク - Rからc++コードを呼び出すRcpp+inlineを試す
Pocket