おさかなせいざ

おさかなせいざ

プログラミングメモや日記がわりに

PackageCompiler と高速な実行

※注意 本記事が書かれたのは2020/04/04現在 (https://github.com/JuliaLang/PackageCompiler.jl) にある PackageCompilerX.jl からforkされたものではなく、それ以前のものに対する記事です 現在と使用体系が異なる可能性があるためご注意下さい

 この記事は,Julia Advent Calendar 2018の18日目の記事です. 17日目のphigasui - Qiitaさんは17日時点ではまだ掲載されていません. リンクがわかり次第追記します.

Julia いいよね

 みなさん Julia 使ってますか?

Julia いいですよね.

C のように速く, Python のように書きやすく, MATLAB のような高度な数値計算もできる(https://julialang.org). そんな甘い謳い文句に誘われ私も Julia を書き初めました.

 噂の通り Julia は大変書きやすく,また高速に動作します. 数値計算やコンピューティングに関して多くのコミュニティがあり, Github 上にも各分野に特化した Organization があります.

 去年度もそうでしたが, 今年もハマっている言語で学園祭の紹介ページを建てようと考えたので, Julia の Genie.jl というフレームワークを使用し, こんなページの Web サーバサイドを書きました (フロントエンドの多くは友人たちが作ってくれました). まだ他言語のような Web における有名なフレームワークはないようですが, 皆 Web 大好きですので,これからどんどんとでてくるのではないでしょうか.

それはそれとして起動速度が問題

 さて,バリバリと勢いにのる Julia ですが,現在ある問題(だと自分は思っている)を抱えています.
それは,初回の実行が遅いことです. Julia の動作手順はInitialization of the Julia runtime · The Julia Languageに詳しく書かれていますが, かいつまむと起動,パース,JIT Compile,実行,といった流れです. この中で実行までに最も時間がかかる処理が JIT Compile だと思われます.
Julia では関数ごとに型推論を行い,型を確定して生成できる関数について実行時コンパイルを行います. これがどのくらいかかるかというと AtCoder では Julia v0.5 において JIT Compile 時に, ほとんど時間を持っていかれるとのことです.

ただし,Julia で問題に回答されている方もいらっしゃるので,まったく出来ないというわけではないようです. また,julia v0.5 から v1.0 では多くの最適化が行われているので,AtCoder では現状より高速になると考えられます. しかし,JIT Compile に時間がかかるのは間違いないありません. そこで PackageCompiler.jl が使えるのではないかと考えました.

PacakgeCompiler.jl

 特に断りがない限り Ubuntu 18.04 で以下の環境を想定しています.

Julia Version 1.0.2
Commit d789231e99 (2018-11-08 20:11 UTC)
Platform Info:
  OS: Linux (x86_64-pc-linux-gnu)
  CPU: Intel(R) Core(TM) i7-4710MQ CPU @ 2.50GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-6.0.0 (ORCJIT, haswell)

 PackageCompiler は Julia のコード(Module)を BaseCoreなどの基本モジュール, 実行に必要な諸々を含め共有ライブラリ(Linux であれば .so)にコンパイルしてまとめるパッケージです. 必要であれば,C言語から共有ライブラリの特定の関数を呼び出す形で実行ファイル生成してくれます.

 それでは PackageCompiler.jl を追加しましょう.juliaコマンドを実行して,]でパッケージモードに入ります.

(v1.0) pkg> add PackageCompiler
  Updating registry at `~/.julia/registries/General`
  Updating git-repo `https://github.com/JuliaRegistries/General.git`
 Resolving package versions...
 Installed ProgressMeter ───── v0.9.0
 Installed Distances ───────── v0.7.4
 Installed DecFP ───────────── v0.4.7
 Installed OffsetArrays ────── v0.9.1
 Installed Reactive ────────── v0.8.3
 Installed CategoricalArrays ─ v0.5.2
 Installed GtkReactive ─────── v0.5.3
  Updating `~/.julia/environments/v1.0/Project.toml`
 [no changes]
  Updating `~/.julia/environments/v1.0/Manifest.toml`
  [324d7699] ↑ CategoricalArrays v0.5.1 ⇒ v0.5.2
  [55939f99] ↑ DecFP v0.4.6 ⇒ v0.4.7
  [b4f34e82] ↑ Distances v0.7.3 ⇒ v0.7.4
  [27996c0f] ↑ GtkReactive v0.5.2 ⇒ v0.5.3
  [6fe1bfb0] ↑ OffsetArrays v0.9.0 ⇒ v0.9.1
  [92933f4c] ↑ ProgressMeter v0.8.0 ⇒ v0.9.0
  [a223df75] ↑ Reactive v0.8.2 ⇒ v0.8.3
  Building DecFP → `~/.julia/packages/DecFP/XA6lk/deps/build.log`

表示が異なることがあるかと思いますが,エラーがなければ問題ありません.

PackageCompiler には juliac.jl というコマンドラインツールが含まれています. 今回はこのコマンドラインツールを使用してコンパイルを行ってみましょう.

 以下が比較するコードです.竹内関数です.
jtest.jljulia jtest.jl と実行するコードです.
test.jlにはC言語から呼び出される関数が macro と共に定義されています.

ではコンパイルです.

$ julia ~/.julia/packages/PackageCompiler/jBqfm/juliac.jl -vaer test.jl 

を実行して

All done

がでればコンパイル完了です.

.
├── builddir
│   ├── test
│   ├── test.a
│   └── test.so
├── jtest.jl
└── test.jl

ディレクトリは上記のような構成になります. builddir/testが実行ファイルです.

それでは複数回実行し,バイナリとコードの実行時間を比べてみます.

$ time for i in `seq 1 10`; do  julia jtest.jl; done
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861

real    0m5.055s
user    0m6.445s
sys 0m5.894s

$ time for i in `seq 1 10`; do ./builddir/test; done
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861
tak(12,6,0) = 12
c = 12604861

real    0m4.899s
user    0m6.496s
sys 0m5.779s

バイナリになってちょっとだけ速くなった...?
あまりぱっとしません. 実行してみた方は分かるかと思いますが, ほんのすこし実行速度が早くなっている代わりに,それ以上のコンパイル時間を必要とします. Julia そのものを build しているのと変わらないでそれはそうです.

しかし,共有ライブラリを生成することで多くの環境で動作させるられるので, 配布する目的であれば julia のない環境でも動作し大変有用です.

ただし,多くのものを共有ライブラリに含めるため,ファイルサイズが大きくなります.

$ ls -l builddir/test*
-rwxrwxr-x 1 pisces pisces     13176 12月 17 22:38 builddir/test
-rw-rw-r-- 1 pisces pisces 130327352 12月 17 22:38 builddir/test.a
-rwxrwxr-x 1 pisces pisces 125952168 12月 17 22:38 builddir/test.so

おそらく libjulia.so などを適宜設定をすれば回避可能と思いますが,詳しくないためここでは省きます.

結論

 結果から,実行速度を向上させるという目的では PackageCompiler.jl は適切ではないと考えられます. C 言語からの活用,配布といった目的にあったパッケージであると感じました.

蛇足

 以下は PackageCompiler 以外の方法で高速に julia を起動実行するのに必要そうなことです.

.ji ファイル?

.ji は Julia の precompile 済みファイルである. 単純に .jl ファイルを実行するだけでは生成されない. 基本的には add 済みの Module がusing, import, requireによって読み込まれた際に生成される. 先程追加した PackageCompiler であればおそらく ~/.julia/compiled/v1.0/PackageCompiler/ にjiファイルがあるはずである.

Julia v1.0 ドキュメントjulia コマンド の switches には記載がないが, Julia v0.6 ドキュメントには

--output-ji name Generate a system image data file (.ji)

の通り.jiの記述がある. issue など流し読みしたところでは,Julia 自体の開発用途以外でこれらの switch は使用されること稀なため, このページ,およびコマンドヘルプからは削除されたようである(Developer Document には記載がある). precompile は内部的にはこの switch を使用している. ライブラリを使用する際, precompile 済みのものを使用したい場合には以下を実行する.

$ julia --compiled-modules=yes 

通常 Pakcage のアップデートがない限り,通常 precompile 済みのものが使用される. 試しに,--compiled-modules=noとしてusing Images, TestImages, ImageView(JuliaImages のパッケージ)としたところ

$ time julia --compiled-modules=no -e "using Images, TestImages, ImageView"

real    0m48.914s
user    0m48.800s
sys 0m0.801s

$ time julia --compiled-modules=yes -e "using Images, TestImages, ImageView"

real    0m9.838s
user    0m9.827s
sys 0m0.722s

precompile を使用するかどうかで5倍以上も速度が異なる.
ここから分かるとおり,.jl ではなく .ji から実行できれば JIT Compile も少なく高速な動作を期待できると考える. しかし,現状では .ji を自体を実行する手段がない,もしくは私が単純に見つけられていないため, 知っている方がいらっしゃればご教授願いたい. 基本的に Package としてある Module のみを対象にしているようである1

高速実行

 方法として一応,Package にして main の内容を __init__ に指定,git の管理下に置き pkg> add . とすることで, $ julia -e 'using Hoge' と2度(一度目に precompile される)すれば,高速に実行できなくもないが手順が多すぎる.

いい感じの方法が見つかったらまた書きます.(見つけたら うおざ (@p1scesCom) | Twitter に教えてもらえると嬉しいです)

Julia よくなってくね

 ここまで読んでいただき有難うございます.

 先程確認したところドキュメントに julia v1.0.3 が追加されていました. julia は毎月の勢いでアップデートしていますね.すごいです. 変化に対応しながら,今後も継続して Julia を使っていきたいと思います.

 明日はY_oHr_N - Qiitaさんの記事です.