MyEnigma

とある自律移動システムエンジニアのブログです。#Robotics #Programing #C++ #Python #MATLAB #Vim #Mathematics #Book #Movie #Traveling #Mac #iPhone

Juliaのコードをコンパイルして高速実行する方法

 

目次

注意 (2020年8月追記)

この記事は非常に古く、Julia 1.0以降では利用できません。。。

現時点で単一バイナリを作る方法はなくなってしましましたが、

Juliaのランタイムも含めて一つのディレクトリにまとめるのは、

PackageCompiler.jlで実現可能です。

github.com

 

はじめに

最近、Juliaというプログラミング言語にハマっているのですが、

myenigma.hatenablog.com

Juliaを使って、

高計算パフォーマンスなシステムを構築したい時に

やはりコンパイルできたらいいなと思ってしまいます。

 

Juliaは動的な言語なので、

実行時にJITコンパイラを使ってコンパイルをしながら

コードを実行するのですが

すでにコードがfixされたコードであっても、

実行するたびにコンパイルする必要があるため、

その分、実行が遅くなりがちです。

 

しかし最近、下記の記事や、

下記のリポジトリのライブラリなどのように、

github.com

Juliaをコンパイルする取り組みが始まっているので、

今回はJuliaをコンパイルし、実行する手法を紹介したいと思います。

 

Juliaにおけるコンパイル

下記の記事をベースに

Juliaにおけるコンパイルについて概要を説明したいと思います。

 

Juliaをコンパイルしたい場合、

原理的には、eval, マクロ, generated functionを

使わないJuliaのコードであれば静的にコンパイルすることができます。

 

加えて、Juliaの基本モジュールの大半は、

型安定性を考慮して設計されているため、

静的にコンパイルし易い形で構成されています。

 

従って、ある特定のJuliaのコードであれば、

特別なトリック無しで静的コンパイルが可能であるということです。

 

例えば、シンプルな方法だと、

下記のコマンドで, test.jlのjuliaのコードを

juliaのinterfaceファイルに変換し、

$ julia --output-jl interface.jl test.jl

続いて、下記のコマンドでinterfaceファイルから

オブジェクトファイルを生成できます。

$ ./julia --output-o sys.o --sysimage inference.ji --startup-file=no sysimg.jl

最後に生成されたオブジェクトファイルを

C言語のコンパイラでコンパイルすれば、

静的コンパイルが可能になります。

$ cc -o julia-app sys.o repl.c -ljulia

 

Juliaのコードをコンパイルするスクリプトjuliac

下記のGithubリポジトリに、

前述の手法を使ってJuliaのコードを静的にコンパイルするための、

公式ライブラリが公開されています。

github.com

 

使い方としては、こちらのリポジトリにある通り、

リポジトリをcloneしてきて、

リポジトリ内のjuliac.jlというスクリプトを使って、

下記のようにコンパイルしたいコード(下記の場合はhello.jl)を指定するだけです。

julia juliac.jl hello.jl

うまくいくと、

builddirというディレクトリができて、

その中に実行ファイルや、オブジェクトファイル、

ダイナミックライブラリのファイルなどが生成されます。

(サンプルのhello.jlは、UnicodePlotsというライブラリに依存しているので、

事前にPkg.add("UnicodePlots")でインストールする必要があります。)

 

あとは、builddir/helloを実行すると、

下記のような実行結果が表示されるはずです。

f:id:meison_amsl:20171008102322p:plain

  

JuMPの最適化コードをjuliacでコンパイルして高速化してみる

Juliaコードにおけるコンパイルの有効性を確認するために、

以前紹介したJuliaの最適化ライブラリであるJuMPを使用した、

ナップサック問題も最適化問題で簡単なベンチマークをとってみたいと思います。

ナップザック問題の概要については下記の記事を参照ください。

myenigma.hatenablog.com

 

今回は下記のようなコードを、

通常の方法でJuliaで実行する方法と、

juliacでコンパイルして実行する方法を比較してみました。

module mystuff

using JuMP
using Mosek

function main()
    println("JuMP sample1")
    model = Model(solver=MosekSolver())
    m = 10
    n = 5
    A = [0.832268 0.540747 0.784667 0.355357 0.928707; 0.802036 0.504541 0.430144 0.411091 0.387708; 0.645349 0.0851049 0.71396 0.470207 0.593221; 0.0901346 0.438501 0.871861 0.909309 0.0145537; 0.38814 0.446732 0.0787192 0.411324 0.324244; 0.832039 0.716916 0.345928 0.612039 0.126577; 0.860024 0.302069 0.188148 0.204705 0.9435; 0.426323 0.48622 0.157624 0.737819 0.173054; 0.212276 0.323749 0.50823 0.242557 0.0126005; 0.70361 0.823343 0.116585 0.940804 0.767688]
    b=[0.976362; 0.0278185; 0.16024; 0.250936; 0.730025; 0.414424; 0.468235; 0.272898; 0.253627; 0.824444]
    println(A)
    println(b)

    @variable(model, 0.0<=x[1:size(A,2)]<=1.0)
    @objective(model, Min, sum((A*x-b).^2))

    println("The optimization problem to be solved is:")
    println(model) # Shows the model constructed in a human-readable form

    status = solve(model)
    println("Objective value: ", getobjectivevalue(model)) 
    println("x:",getvalue(x))

end

Base.@ccallable function julia_main(ARGS::Vector{String})::Cint
    main()
    return 0
end

if contains(@__FILE__, PROGRAM_FILE)
    main()
end

end

https://github.com/AtsushiSakai/JuliaSamples/blob/master/juliac/sample1/hello.jlgithub.com

 

下記が通常のjuliaコマンドで実行した結果です。

今回はbashのtimeをjuliaコマンドの前につけて、

JITコンパイルを含めた計算時間計測しています。

下記のターミナルのスクリーンショットを見るとわかるとおり、

プログラム起動から終了まで、通常のjuliaコマンドでは11秒かかっているのがわかります。

f:id:meison_amsl:20171010063315p:plain

 

一方、下記の結果はjuliacでコンパイルした実行ファイルを

実行した場合の結果です。

最適化の結果は同じですが、

0.8秒でプログラム実行から終了までが完了しており、

非常に高速に実行できていることがわかります。

f:id:meison_amsl:20171010063332p:plain

 

juliacを使う時の注意点

自分がハマった注意点をまとめておきます。

現在の所、コンパイルするjuliaのコードはhello.jlという名前でないとCコードを変更する必要がある

コンパイルするjuliaのファイル名を

サンプルプログラムのhello.jl以外にしたい場合は、

下記のコードのライブラリファイル名のhelloの部分を

ファイル名に変更する必要があります。

github.com

(PRを出すべきか。。)

 

PyCallが含まれたコードはコンパイルできない

当たり前ですが、

PyCallで呼ばれたコードはJuliaのコードではないので、

コンパイルすることができません。

 

自分はPyCallでmatplotlibをよく使うので、

Julia製のmatplotlibがあれば、

乗り換えるチャンスかもしれません。

 

JuMPのコードをコンパイルする時は、配列のインデックスの始まりは1にする

もし、JuMPのコードをコンパイルしたい場合、

マクロ内の配列のインデックスは、1始まりにする必要があります。

通常のJuliaの実行方法では0始まりのコードを書けますが、

juliacでコンパイルしようとすると、

The applicable method may be too new: running in world age 26123, while current world is 27569. Closest candidates are: _to_cartesian(::Any, ::Any, ::Any...) at /Users/atsushisakai/.julia/v0.6/JuMP/src/JuMPArray.jl:54 (method too new to be called from this world context.)

というようなエラーが出てコンパイルできません。

インデックスを1始まりにすると治ります。

JuMPの非線形最適化のマクロがあるとコンパイルできない

JuMPは非線形最適化も解くことができますが、

現状、コンパイルはできますが、

実行するとセグフォで落ちてしまいます。

 

コードの中で、@NLconstraintが入っているとだめなようです。

 

参考資料

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

 

MyEnigma Supporters

もしこの記事が参考になり、

ブログをサポートしたいと思われた方は、

こちらからよろしくお願いします。

myenigma.hatenablog.com