日本語での言及がまだないようだったので TruffleRuby + Galaaz の人柱やってみました。GraalVM, R, ggplot2 について詳しくない人が見様見真似で書いています。
(このグラフは graalvm-demos に入っているデモコードを実行して描いたもの)
Galaaz
GraalVM を使って R の機能を Ruby から使うための gem。
作者の Rodrigo Botafogo さんによる解説記事。これらの記事で Galaaz の存在を知りました。
graalvm-demos
This repository contains several small applications. These programs illustrate the capabilities of GraalVM
GraalVM を使うとこんなことができるよ、というデモを集めたリポジトリのようです。いくつかあるデモの1つとして Galaaz が使えるようになっています。
以下、これを使って試しました。
フォーク版を使う場合(おすすめ)
(この節は 2022-06-25 に追記しました)
フォークしてファイルの追加を行った galaaz
ブランチを作りました。java11-21.2.0 を使う場合はこれを使うと簡単に試せます。
https://github.com/sonota88/graalvm-demos/tree/galaaz
git clone https://github.com/sonota88/graalvm-demos.git
cd graalvm-demos
git checkout galaaz
イメージをビルドします。
Dockerfile-galaaz の中を見ると分かりますが、 graalvm-demos で使っているイメージをベースとして、 bzip2
と R のパッケージのインストールを追加しただけです。
cd galaaz-ggplot
# graalvm-demos-galaaz:1 イメージを作成
./build.sh
私の環境では90分程かかりました。
イメージができたら、あとはコンテナを起動して実行するだけ。
./run.sh
# ---- 以下はコンテナ内 ----
cd /graalvm-demos/galaaz-ggplot
ruby --polyglot sample_min.rb
# => sample_min.svg が生成される
ruby --polyglot sample.rb
# => sample.svg が生成される
以上。
この方法で試す場合は、以下に書いている「準備」の手順は全部無視して大丈夫です。
準備
(2022-06-25 追記)
- 後から気付いたのですが、 java11-21.2.0 を使う場合は以下の手順は不要でした。自分でイメージをビルドしても
runDockerImage.sh
では無視されてしまいます。 - 上述の「フォーク版を使う場合」の方法で試す場合もこの手順は不要です。
Dockerfile などの修正
ダウンロード対象が存在しなくなっていてビルドに失敗するので、現在でもダウンロードできるバージョンに修正。
--- a/docker-images/Dockerfile
+++ b/docker-images/Dockerfile
@@ -99,9 +99,9 @@ ENV PATH=${GRADLE_HOME}/bin:${PATH}
RUN echo " --- Gradle version:"; gradle --version; echo
# Install jmeter
-RUN cd /tmp/; wget -q -nv "https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.4.1.zip"
-RUN unzip /tmp/apache-jmeter-5.4.1.zip
-ENV JMETER_HOME="${WORKDIR}/apache-jmeter-5.4.1"
+RUN cd /tmp/; wget -q -nv "https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.4.3.zip"
+RUN unzip /tmp/apache-jmeter-5.4.3.zip
+ENV JMETER_HOME="${WORKDIR}/apache-jmeter-5.4.3"
ENV PATH=${JMETER_HOME}/bin:${PATH}
RUN echo " --- Jmeter version:"; jmeter --version; echo
--- a/docker-images/buildDockerImage.sh
+++ b/docker-images/buildDockerImage.sh
@@ -24,7 +24,7 @@ FULL_GRAALVM_VERSION="${1:-"${DEFAULT_GRAALVM_VERSION}"}"
FULL_DOCKER_TAG_NAME="graalvm-demos"
GRAALVM_HOME_FOLDER="/opt/graalvm"
-MAVEN_VERSION="3.8.4"
+MAVEN_VERSION="3.8.6"
GRADLE_VERSION="7.2"
SCALA_VERSION="3.0.2"
SBT_VERSION="1.5.5"
試していませんが、 Galaaz を試す分には必要なさそうなので JMeter と Maven をダウンロードしている箇所をコメントアウトしてもいいかもしれません。
イメージのビルド
所要時間は30分弱。
cd docker-images
time ./buildDockerImage.sh
引数で指定しなければデフォルトで JDK 11 + GraalVM 21.2.0 になります。
最新のバージョンではありませんが、下手に触らずデフォルト設定で進めます。
できあがったイメージのサイズは 4.24GB。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
neomatrix369/graalvm-demos java11-21.2.0 c9ad84ce5a6a 7 months ago 4.24GB
(他のイメージは省略)
Galaaz 専用のイメージではなく他のデモでも使う共通のイメージなので、Galaaz を試すのには関係ないものも含まれています。必要なものに絞ればイメージサイズとビルド時間などが節約できるかもしれません。
コンテナ起動
cd .. # リポジトリのルートに戻る
./runDockerImage.sh
環境変数 DEMO_TYPE
に gui
を指定して実行すると VNC を利用してグラフィカルな機能も利用できますが、この記事の内容的には不要なので GUI なしで使います。
以下、コンテナ内での操作。
とりあえず env。
root@9fa633e4b18f:/graalvm-demos# env | sort
GRAALVM_HOME=/opt/graalvm
GRADLE_HOME=/gradle-7.2
HOME=/root
HOSTNAME=9fa633e4b18f
JAVA_HOME=/opt/graalvm
JDK8_HOME=/usr/lib/jvm/java-1.8.0-openjdk-amd64
JMETER_HOME=/apache-jmeter-5.4.1
LANG=en_US.UTF-8
LANGUAGE=en_US
M2_HOME=/usr/local/apache-maven/apache-maven-3.8.3/
MICRONAUT_HOME=/root/.micronaut/micronaut-cli
PATH=/root/.micronaut/micronaut-cli/bin:/apache-jmeter-5.4.1/bin:/gradle-7.2/bin:/usr/local/apache-maven/apache-maven-3.8.3//bin:/opt/graalvm/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/graalvm-demos
SCALA_HOME=/usr/share/scala
SHLVL=0
TERM=xterm
_=/usr/bin/env
gu コマンドでインストール状況を見てみます。
root@9fa633e4b18f:/graalvm-demos# gu --version
GraalVM Updater 21.2.0
root@9fa633e4b18f:/graalvm-demos# gu list
ComponentId Version Component name Stability Origin
---------------------------------------------------------------------------------------------------------------------------------
graalvm 21.2.0 GraalVM Core -
R 21.2.0 FastR Experimental github.com
espresso 21.2.0 Java on Truffle Experimental github.com
js 21.2.0 Graal.js Supported
llvm-toolchain 21.2.0 LLVM.org toolchain Supported github.com
native-image 21.2.0 Native Image Early adopter github.com
nodejs 21.2.0 Graal.nodejs Supported github.com
python 21.2.0 Graal.Python Experimental github.com
ruby 21.2.0.1 TruffleRuby Experimental github.com
諸々インストール済みになっていますね。
今回は最低限 ruby
と R
だけあればよさそう?
Ruby(TruffleRuby) と R(FastR) のバージョン。
root@9fa633e4b18f:/graalvm-demos# ruby -v
truffleruby 21.2.0.1, like ruby 2.7.3, GraalVM CE Native [x86_64-linux]
root@9fa633e4b18f:/graalvm-demos# R --version
R version 4.0.3 (FastR)
... snip ...
/opt/graalvm/bin
を見てみます。
root@9fa633e4b18f:/graalvm-demos# ls -lF /opt/graalvm/bin/ | grep ruby
lrwxrwxrwx 1 root root 28 Oct 19 2021 bundle -> ../languages/ruby/bin/bundle*
lrwxrwxrwx 1 root root 29 Oct 19 2021 bundler -> ../languages/ruby/bin/bundler*
lrwxrwxrwx 1 root root 25 Oct 19 2021 erb -> ../languages/ruby/bin/erb*
lrwxrwxrwx 1 root root 25 Oct 19 2021 gem -> ../languages/ruby/bin/gem*
lrwxrwxrwx 1 root root 25 Oct 19 2021 irb -> ../languages/ruby/bin/irb*
lrwxrwxrwx 1 root root 26 Oct 19 2021 racc -> ../languages/ruby/bin/racc*
lrwxrwxrwx 1 root root 28 Oct 19 2021 racc2y -> ../languages/ruby/bin/racc2y*
lrwxrwxrwx 1 root root 26 Oct 19 2021 rake -> ../languages/ruby/bin/rake*
lrwxrwxrwx 1 root root 26 Oct 19 2021 rdoc -> ../languages/ruby/bin/rdoc*
lrwxrwxrwx 1 root root 24 Oct 19 2021 ri -> ../languages/ruby/bin/ri*
lrwxrwxrwx 1 root root 26 Oct 19 2021 ruby -> ../languages/ruby/bin/ruby*
lrwxrwxrwx 1 root root 33 Oct 19 2021 truffleruby -> ../languages/ruby/bin/truffleruby*
lrwxrwxrwx 1 root root 28 Oct 19 2021 y2racc -> ../languages/ruby/bin/y2racc*
/opt/graalvm/languages/ruby/bin
以下にあるものへのシンボリックリンクになっているので、そっちも見てみます。
root@9fa633e4b18f:/graalvm-demos# ls -lF /opt/graalvm/languages/ruby/bin
total 173876
-rwxr-xr-x 1 root root 1211 Oct 19 2021 bundle*
-rwxr-xr-x 1 root root 1213 Oct 19 2021 bundler*
-rwxr-xr-x 1 root root 5745 Oct 19 2021 erb*
-rwxr-xr-x 1 root root 552 Oct 19 2021 galaaz*
-rwxr-xr-x 1 root root 1227 Oct 19 2021 gem*
-rwxr-xr-x 1 root root 550 Oct 19 2021 gknit*
-rwxr-xr-x 1 root root 562 Oct 19 2021 gknit-draft*
-rwxr-xr-x 1 root root 548 Oct 19 2021 grun*
-rwxr-xr-x 1 root root 554 Oct 19 2021 gstudio*
-rwxr-xr-x 1 root root 567 Oct 19 2021 htmldiff*
-rwxr-xr-x 1 root root 1189 Oct 19 2021 irb*
-rwxr-xr-x 1 root root 561 Oct 19 2021 ldiff*
-rwxr-xr-x 1 root root 1268 Oct 19 2021 racc*
-rwxr-xr-x 1 root root 1272 Oct 19 2021 racc2y*
-rwxr-xr-x 1 root root 1195 Oct 19 2021 rake*
-rwxr-xr-x 1 root root 1195 Oct 19 2021 rdoc*
-rwxr-xr-x 1 root root 1191 Oct 19 2021 ri*
-rwxr-xr-x 1 root root 566 Oct 19 2021 rspec*
lrwxrwxrwx 1 root root 11 Oct 19 2021 ruby -> truffleruby*
-rwxr-xr-x 1 root root 177962856 Oct 19 2021 truffleruby*
-rwxr-xr-x 1 root root 1272 Oct 19 2021 y2racc*
ruby
は truffleruby
へのシンボリックリンク。
gem environment
も見てみます。
root@9fa633e4b18f:/graalvm-demos# gem environment
RubyGems Environment:
- RUBYGEMS VERSION: 3.1.6
- RUBY VERSION: 2.7.3 (2021-07-29 patchlevel 0) [x86_64-linux]
- INSTALLATION DIRECTORY: /opt/graalvm/languages/ruby/lib/gems
- USER INSTALLATION DIRECTORY: /root/.gem/truffleruby/2.7.3.21.2.0
- RUBY EXECUTABLE: /opt/graalvm/languages/ruby/bin/truffleruby
- GIT EXECUTABLE:
- EXECUTABLE DIRECTORY: /opt/graalvm/languages/ruby/bin
- SPEC CACHE DIRECTORY: /root/.gem/specs
- SYSTEM CONFIGURATION DIRECTORY: /b/b/e/truffleruby/etc
- RUBYGEMS PLATFORMS:
- ruby
- GEM PATHS:
- /opt/graalvm/languages/ruby/lib/gems
- /root/.gem/truffleruby/2.7.3.21.2.0
- GEM CONFIGURATION:
- :update_sources => true
- :verbose => true
- :backtrace => false
- :bulk_threshold => 1000
- "gem" => "--no-document"
- REMOTE SOURCES:
- https://rubygems.org/
- SHELL PATH:
- /root/.micronaut/micronaut-cli/bin
- /apache-jmeter-5.4.1/bin
- /gradle-7.2/bin
- /usr/local/apache-maven/apache-maven-3.8.3//bin
- /opt/graalvm/bin
- /usr/local/sbin
- /usr/local/bin
- /usr/sbin
- /usr/bin
- /sbin
- /bin
galaaz gem がインストール済みであることを確認。
root@9fa633e4b18f:/graalvm-demos# gem list | grep galaaz
galaaz (0.5.0)
root@9fa633e4b18f:/graalvm-demos# ls -lF /opt/graalvm/languages/ruby/lib/gems/gems/ | grep galaaz
drwxr-xr-x 9 root root 4096 Oct 19 2021 galaaz-0.5.0/
追加のインストール
ggplot2 のインストール時に
error reading connection: Cannot run program "bzip2": error=2, No such file or directory
というエラーが出て失敗するので、先に bzip2 をインストールしておきます。
apt update
apt install bzip2
散布図を描く
graalvm-demos の galaaz-ggplot/README.md などを参考にしつつ、まずは最低限のもの。
# sample_min.rb
require "galaaz"
require "ggplot" # ... r_requires/ggplot.rb
d = R.data__frame(
x: R.c( 1, 2, 4, 0.3, 2.5, 5.1),
y: R.c(12, 13, 26, 22, 29, 54)
)
gg = d.ggplot(E.aes(x: :x, y: :y)) +
R.geom_point()
R.svg("sample_min.svg")
puts gg
R.dev__off
実行する際、--polyglot
オプションを付けずに単に ruby sample_min.rb
と実行するとこうなります。
root@9fa633e4b18f:/graalvm-demos/galaaz-ggplot# ruby sample_min.rb
/opt/graalvm/languages/ruby/lib/gems/gems/galaaz-0.5.0/lib/R_interface/r.rb:26:in `eval_file': No language for id R found. Supported languages are: [ruby] (ArgumentError)
from /opt/graalvm/languages/ruby/lib/gems/gems/galaaz-0.5.0/lib/R_interface/r.rb:26:in `<top (required)>'
from <internal:core> core/kernel.rb:293:in `require_relative'
from /opt/graalvm/languages/ruby/lib/gems/gems/galaaz-0.5.0/lib/galaaz.rb:26:in `<top (required)>'
from <internal:core> core/kernel.rb:234:in `gem_original_require'
from /opt/graalvm/languages/ruby/lib/mri/rubygems/core_ext/kernel_require.rb:158:in `require'
from <internal:core> core/unbound_method.rb:18:in `bind_call'
from <internal:core> core/kernel.rb:272:in `require'
from sample_min.rb:1:in `<main>'
<internal:core> core/kernel.rb:236:in `gem_original_require': cannot load such file -- galaaz (LoadError)
from /opt/graalvm/languages/ruby/lib/mri/rubygems/core_ext/kernel_require.rb:83:in `require'
from <internal:core> core/unbound_method.rb:18:in `bind_call'
from <internal:core> core/kernel.rb:272:in `require'
from sample_min.rb:1:in `<main>'
Ruby から R のプログラムを実行しようとしたところで No language for id R found
と言われています。
(メモ: require "galaaz" したときの呼び出しの流れ)
requrie "galaaz"
=> galaaz.rb
=> R_interface/r.rb
=> R_interface/r_libs.R
--polyglot
オプションを付けて実行しましょう。
time ruby --polyglot sample_min.rb
実行すると、初回の場合は R のライブラリのインストールが始まります。
↓こういうのがインストールされるようです。
# R_interface/r_libs.R
list.of.packages <- c('rlang', 'purrr', 'formula.tools', 'lobstr', 'rticles')
# r_requires/ggplot.rb
R.install_and_loads('ggplot2', 'grid', 'gridExtra')
私の環境だと70分くらいかかりました(初回実行時のみです。念のため)。
実行が終わると sample_min.svg
ができています。
上述の「フォーク版を使う場合」の方法で試す場合は、必要なパッケージがインストール済みになっているため、すぐに実行が完了します。
(ちなみにこの画像はコンテナ外で Inkscape を使って PNG に変換したもの)
タイトル、軸ラベル、日本語テキスト、複数系列、回帰直線を含めたものを描いてみました。
# sample.rb
require "galaaz"
require "ggplot"
d = R.data__frame(
x: R.c( 1, 2, 4, 0.3, 2.5, 5.1),
y: R.c(12, 13, 26, 22, 29, 54),
type: R.c("系列1", "系列1", "系列1", "系列2", "系列2", "系列2")
)
gg = d.ggplot(E.aes(x: :x, y: :y, color: :type)) +
R.geom_point() +
R.xlim(R.c(0, 6)) +
R.ylim(R.c(0, 60)) +
R.geom_smooth(method: "glm", se: false) +
R.ggtitle("タイトル") +
R.xlab("x軸ラベル") +
R.ylab("y軸ラベル")
R.svg("sample.svg")
puts gg
R.dev__off
というわけで、Galaaz を使って簡単な散布図を描くところまでやってみました。
graalvm-demos が便利だな、というのと、Galaaz は GraalVM の polyglot 機能の実際の利用例・デモとしてもおもしろいな、というのが素朴な感想です。
あと、以前から GraalVM に興味はあったのですが、きっかけがなくて触れていませんでした。今回実際に触ってみて雰囲気が知れてよかったです。
以下、適当なメモ。
メモ
たとえば R で c(...)
と書くところを、Galaaz では R.c(...)
と書く。
……と聞くとピンとくるかもしれませんが、 method_missing が使われています。
github.com/rbotafogo/galaaz/blob/v0.4.9/lib/R_interface/r.rb
data.frame(...)
や dev.off(...)
のようにドットを含む場合は
R.data__frame(...)
, R.dev__off(...)
のようにアンダースコア2つ書く(R::Support.parse_arg
で __
を .
に変換している)。
実際に R の機能を呼び出している箇所を探すと、次のように Polyglot.eval
が使われている箇所が見つかります。
github.com/rbotafogo/galaaz/blob/v0.4.9/lib/R_interface/rsupport.rb
# 例として R::Support.eval
def self.eval(string)
begin
Polyglot.eval("R", string)
rescue RuntimeError
raise NoMethodError.new("Method #{string} not found in R environment")
end
end
R
は分かったけど E.aes
の E
は何?
これは R の遅延評価を Ruby でエミュレートするためのもののようです。
In Ruby, there is no lazy evaluation and doing R.aes would try to evaluate aes immediately. In order to delay the evaluation of function aes we need to use E.aes.
method_missing をうまく利用しているためか、意外と(?)コード量は少なめ。
galaaz の現在の master 7ebe763 では、雑にコメント行を除くと 2,000行ちょっと。
$ find lib -name '*.rb' | xargs cat | grep -v '^ *#' | wc -l
2144