search
LoginSignup
1

posted at

updated at

Galaaz を触ってみた(TruffleRuby + ggplot2 で散布図を描いてみた)

日本語での言及がまだないようだったので TruffleRuby + Galaaz の人柱やってみました。GraalVM, R, ggplot2 について詳しくない人が見様見真似で書いています。

image.png
(このグラフは 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 では無視されてしまいます。 :sweat:
  • 上述の「フォーク版を使う場合」の方法で試す場合もこの手順は不要です。

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_TYPEgui を指定して実行すると 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

諸々インストール済みになっていますね。
今回は最低限 rubyR だけあればよさそう?


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*

rubytruffleruby へのシンボリックリンク。


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 ができています。

上述の「フォーク版を使う場合」の方法で試す場合は、必要なパッケージがインストール済みになっているため、すぐに実行が完了します。

image.png

(ちなみにこの画像はコンテナ外で 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

image.png


というわけで、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.aesE は何?

これは 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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1