こんな話がある。
で、これRからもできるのでは?と思ってやってみようとしたらいろいろハマってつらかった話をします。
Rustとは
Rustは、Mozillaが開発しているプログラミング言語です。
- はやい
- “drop-in replacement” for Cを目指している
みたいなのが私の中でのイメージです(つまりあんまりよく知らない)。
なぜ私がRustに興味を持ったかというと、もしかしてRustを覚えればCとかC++触らなくてもいいのでは??という気がしたからです。WindowsでC/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ビット版で設定を分けないといけないのに一つしかないからどっちかで詰まる、という。
なので、重い腰を上げて最新版に対応させます。つらい...。
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ビット版をビルド
CC
とCXX
で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を何度再インストールしたことか。
なんというインガ・オホー。つらい。。
追伸
あまりに思い詰めてこんなつぶやきをしてしまいましたが、もうブログに書いちゃったので話さないかも。
@yokkuns LTやりたいです。タイトルは「RからRustの関数をつかう → はやい」でお願いします。
— Hiroaki Yutani (@yutannihilation) November 7, 2015