「RからRustの関数をつかう → はやい」と言おうとしたらめっちゃ時間がかかった話

こんな話がある。

qiita.com

qiita.com

で、これRからもできるのでは?と思ってやってみようとしたらいろいろハマってつらかった話をします。

Rustとは

www.rust-lang.org

Rustは、Mozillaが開発しているプログラミング言語です。

みたいなのが私の中でのイメージです(つまりあんまりよく知らない)。

なぜ私がRustに興味を持ったかというと、もしかしてRustを覚えればCとかC++触らなくてもいいのでは??という気がしたからです。WindowsC/C++のビルド環境を整えるのとか結構大変です。かたやRustはインストーラーでさくっとインストールできます。

そんなかすかな希望を胸に抱きつつ、ちょっと試してみます。

FFIとは

Foreign Function Interface、別の言語の関数を使うためのインターフェースです。あんまり理解できてないですが、たぶんRcppがやってることがそれなんだと思っています。

libffi

RcppはRとC++の間に特化していますが、それをもっと低いレイヤーで提供してくれるライブラリがlibffiです。

FFI stands for Foreign Function Interface. A foreign function interface is the popular name for the interface that allows code written in one language to call code written in another language. The libffi library really only provides the lowest, machine dependent layer of a fully featured foreign function interface. A layer must exist above libffi that handles type conversions for values passed between the two languages.
(https://sourceware.org/libffi/)

ここに書かれているように、低いレイヤーなので、その上にひとつレイヤーをかまさないといけません。Rの場合には、Rffiというパッケージがあります。

Rffi

RJSONIO、XML、RCurlなどなど、Hadleyverse以前からRを支えてきたDuncan氏のパッケージです。

この人ほんと元祖シリパクというにふさわしい。なんでも作ってるな...。

で、ヤッター、と思ってインストールしようとしてみたんですが、なんかエラーがでます。。

Rtools

これは、前回記事に書いたRtools3.3対応できていない問題な気がします。32ビット版と64ビット版で設定を分けないといけないのに一つしかないからどっちかで詰まる、という。

notchained.hatenablog.com

なので、重い腰を上げて最新版に対応させます。つらい...。

libffiを自分でビルド

ビルドする環境はMSYS2です。MobaXtermはなんか32ビット版しかむりっぽくてあきらめました。

ソースコードをダウンロードしてきます。

wget ftp://sourceware.org/pub/libffi/libffi-3.2.1.tar.gz
tar xf libffi-3.2.1.tar.gz && cd libffi-3.2.1

64ビット版をビルド

CCCXXでRtoolsのGCCを指定します。あと、CFLAGS--buildには64ビット用のオプションを指定します。

./configure CC="/c/Rtools/gcc-4.6.3/bin/gcc.exe" CXX="/c/Rtools/gcc-4.6.3/bin/g++.exe" CFLAGS="-m64" --build=x86_64-w64-mingw32
cd x86_64-w64-mingw32
make

32ビット版をビルド

cd ..
./configure CC="/c/Rtools/gcc-4.6.3/bin/gcc.exe" CXX="/c/Rtools/gcc-4.6.3/bin/g++.exe" CFLAGS="-m32" --build=i686-w64-mingw32
cd i686-w64-mingw32
make

必要なファイルをコピー

.libs/というディレクトリに拡張子.aになっているファイルがあるので、これをコピーします。

cd ..

cp x86_64-w64-mingw32/.libs/libffi.a /path/to/lib/x64/
cp i386-w64-mingw32/.libs/libffi.a /path/to/lib/i386/

ヘッダファイルはどちらか片方ですが、少しだけ差分があって、どちらを置けばいいのかよくわかりませんでした。。たぶんメインで使うのは64ビット版なのでそっちを使うことにしましたがあってるのか自信がないです。

$ diff {x86_64,i686}-w64-mingw32/include/ffi.h
61,62c61,62
< #ifndef X86_WIN64
< #define X86_WIN64
---
> #ifndef X86_WIN32
> #define X86_WIN32

Makevars.winとかもろもろを修正

詳細は省略。なんか↓のワーニングが出るのが不安ですが、とりあえず動くようにはなりました。

Warning message:
multiple methods tables found for ‘addFinalizer’ 

インストール方法は以下のような感じです。

# 依存パッケージをインストール
devtools::install_url("http://www.omegahat.org/RAutoGenRunTime/RAutoGenRunTime_0.3-1.tar.gz")

# Rffiをインストール
devtools::install_github("yutannihilation/Rffi")

いよいよRからRustの関数をつかう

Rustの関数

こんな感じのCargo.tomlを置いて、fib.dllがビルドされるようにします。

[package]
name = "fib"
version = "0.1.0"
authors = ["yutannihilation <XXXXXXXX@example.com>"]

[lib]
name = "fib"
crate-type = ["dylib"]

Rの関数

なんかよくわからないけど、こんな感じの関数を作ったら動いた。cifに戻り値と引数の型を指定して、symにRustのシンボル名を指定します。

fib <- function(x){
  Rffi::callCIF(
    cif = Rffi::prepCIF(retType  = Rffi::uint32Type, argTypes = list(Rffi::uint32Type)),
    sym = "fib",
    x
  )
}

fib(40L)
#> [1] 102334155

実際速いのか

よくわかりません。

Unit: milliseconds
               expr      min      lq     mean   median       uq      max neval
 RRustFib::fib(40L) 903.6317 917.594 953.1555 935.9296 964.0231 1568.705   100

ためしにライブラリじゃなくて実行ファイルにしてみたら、なぜかRから使う時より遅いという謎さ...。たぶん何か間違えてる気がする。

Unit: seconds
                         expr     min       lq     mean   median       uq      max neval
 system("c:/path/to/fib.exe") 2.11324 2.225963 2.299703 2.236488 2.331125 2.762006   100

ともあれ、RからRustの関数を使うことに成功しました!

あー、大変だった。

...あれ、そもそも俺はなぜこんなことを?

振り返ってみると

楽をするために始めたはずのRからRustを使う道のり。

気づけば、楽どころかどんどん泥沼にハマっていったし、なぜか避けていたはずのC/C++の環境構築をがっつりやる羽目になりました。MSYS2とRtoolsを何度再インストールしたことか。

なんというインガ・オホー。つらい。。

追伸

あまりに思い詰めてこんなつぶやきをしてしまいましたが、もうブログに書いちゃったので話さないかも。