MyEnigma

とあるエンジニアのブログです。#Robotics #Programing #C++ #Python #MATLAB #Vim #Mathematics #Book #Movie #Traveling #Mac #iPhone

Juliaコードの初回実行を高速化するためのPackageCompiler.jl入門


1から始める Juliaプログラミング

目次

はじめに

これまでPythonでやってきたことを、

Juliaで置き換えようとすると、まず初めにぶつかるのが、

Juliaのコードにおけるライブラリのロードや、

初めて使う関数の計算が遅いなど、

いわゆる初回実行が遅い問題が気になってきます。

(英語ではこの問題をlatencyと呼んでいるそうです

github.com

)

 

Juliaは基本的に初回実行時にコードをJITコンパイルして実行するため、

動的言語でありながら、高速な実行を実現できているので、

myenigma.hatenablog.com

この初回実行が遅いのはある程度はしょうがないのかなと思います。

 

しかし、

毎回使うライブラリのロードや

変更が不要な関数が、

毎回遅いのはなんとかならないのかなと思っていました。

 

そこで、この問題の一つの解決策がPackageCompiler.jlです。

github.com

julialang.github.io

 

概要はこちらの動画を参照していただけるとわかると思いますが、

www.youtube.com

sysimageと呼ばれるJuliaのセッション保存機能を使って、

いつも利用するライブラリをロードした状態や、

関数のJITコンパイル結果を保存することにより、

そのsysimageを使って、juliaコードを実行したときに、

初回実行を高速化することができます。

(Julianのsysimageに関しては、こちらを参照ください

julialang.github.io)

 

今回の記事では、このPackageCompiler.jlとsysimagesを使うことで、

Juliaのコードの初回実行を高速化する方法を簡単に紹介したいと思います。

パッケージのロードをsysimageで高速化する

今回は、自分が最も使用頻度の高いグラフPlotパッケージである

PyPlotのロードを高速化してみたいと思います。

github.com

 

まずはじめに、特になにもせずに

Juliaのセッションを立ち上げて、PyPlotをロードすると、

下記のように5秒ほど時間がかかりました。

f:id:meison_amsl:20200814133300p:plain

 

続いて、sysimageを作成してみます。

まずはじめにPackageCompiler.jlをインストールして

using Pkg;Pkg.add("PackageCompiler");

続いて、PackageCompilerのcreate_sysimageという関数を使って、

julialang.github.io

sysimageのファイルを作成してみます。

using PackageCompiler
create_sysimage([:PyPlot];sysimage_path="sysimage_plots.dylib")

1つ目引数のリストに、

sysimageに含めたいパッケージのsymbol

(パッケージ名の前に:をつけたもの)を入力します。

今回はPyPlotのみを含めるため、:PyPlotのみですが、複数含めることも可能です。

2つ目のsysimage_pathには、作成するsysimageファイルのパスを指定します。

自分はMac OSを使っているので、.dylibファイルを生成していますが、

Linuxの場合は.so, windowsの場合は.dllファイルを指定する必要があります。

他にもデフォルトのsysimageを置き換えたりできます。

詳細は上記のドキュメントを参照ください

 

このコードを実行すると、自分の環境では2分ぐらいかかって、

sysimageのファイルができました。

(ちなみにサイズは166MBありました)

 

続いて、このsysimageを使って、Juliaのセッションを立ち上げてみます。

ターミナルからjuliaコマンドでREPLを起動するときに、

--sysimageで先程のファイルのパスを指定するだけです。

$ julia --sysimage sysimage_plots.dylib

また、-Jでも同じようにsysimageを指定できます。

$ julia -Jsysimage_plots.dylib

そして、先程と同じようにPyPlotをロードしてみると、

1万倍以上速くパッケージをロードできていることがわかります。

f:id:meison_amsl:20200814135609p:plain

これからREPLを起動するときに、

毎回のsysimageを指定して起動するだけで、

同じ効果が得られます。

 

関数の初期実行をsysimagesで高速化する

上記の方法で、パッケージのロードは高速化されましたが、

ロードしたパッケージの関数を実行すると、

残念ながら、初期実行はまだ遅いままです。

f:id:meison_amsl:20200814140557p:plain

これはパッケージのロード情報はsysimageに含まれていますが、

各関数のコンパイル情報はsysimageに含まれていないからです。

 

PackageCompiler.jlを使うと、よく使う関数のコンパイル情報も

sysimageに含むことができます。

まずはじめに、よく使う関数を実行するjulia scriptを作成します。

今回は、PyPlotのplotを高速化したいので、

plotのみを実行する下記のようなシンプルな

Julia script (precompile_pyplot.jl)を作成しました。

using PyPlot
plot(rand(10),rand(10))

続いて、PackageCompilerのcreate_sysimage関数で、

先程の入力に加えて、

precompile_execution_fileという引数で先程のscriptを指定して

sysimageを作成します。

using PackageCompiler
create_sysimage([:PyPlot];sysimage_path="sysimage_plots.dylib", precompile_execution_file="precompile_pyplot.jl")  

これにより、PyPlotのロード情報だけでなく、

指定したJulia scriptを実行した結果として生成される

plot関数のコンパイル情報も含まれたsysimageが生成されます。

(ちなみに、precompile_execution_file以外にも、

precompile_statements_fileという引数もありますが、

この違いに関しては下記を参照ください

discourse.julialang.org)

 

そして、先程と同様に、生成されたsysimageを使って、

PyPlotをロードして、plotを実行すると、

先程の結果を比べて、一回目の実行が3倍ほど高速化されたのがわかります。

f:id:meison_amsl:20200814142225p:plain

 

ただ残念ながら、おそらく何かしらの初回実行処理がまだ必要なため、

一回目から、二回目の実行と同じぐらい速く実行するのはまだ難しそうです。

 

REPL用のデフォルトパッケージをロードしたsysimageを作る便利関数

自分の場合、REPL使ってJuliaのコードを開発するときは、

常に、

github.com

github.com

github.com

github.com

は、常にいつでも使える状態にしておきたいので、

これらのモジュールをロードした状態でのsysimageを

簡単にデフォルトで利用したいと思いました。

 

そこで、下記のようなコードをstartup.jlに追加し、

install_default_pkgs()とすると、指定したデフォルトパッケージをインストールし、

generate_default_sysimage()とすると、

簡単にそれらのパッケージをロードしたsysimageを作成し、

デフォルトのものに置き換えるようにしました。

atreplinit() do repl
    @eval using Pkg

    DEFAULT_PKGS = [:OhMyREPL, :Revise, :BenchmarkTools, :PyPlot]

    function install_default_pkgs()
        println("Installing default packages to latest ones...")
        for pkg in DEFAULT_PKGS
            Pkg.add(String(pkg))
        end
    end

    function generate_default_sysimage()

        println("Updating default packages to latest ones...")
        for pkg in DEFAULT_PKGS
            println("Updating $(String(pkg))")
            Pkg.update(String(pkg))
        end

        create_sysimage(DEFAULT_PKGS;replace_default=true)

        println("Done!!. If you want to restore default sysimage, run `restore_default_sysimage()`")

    end
end

これで、たまに時間があるときに、generate_defalut_sysimage()を呼んで、

sysimageを更新し、あとは高速に開発用のデフォルトパッケージを利用できます。

 

参考資料

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com

myenigma.hatenablog.com


1から始める Juliaプログラミング

MyEnigma Supporters

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

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

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

myenigma.hatenablog.com