Julia - パッケージを効率よく読み込む ~ Requires.jl のすすめ ~

Julia 1.0 が出てから半年経って Julia に慣れてきた頃なのか、私のパッケージの作り方の記事が地味に見られているようなので、パッケージを作るときに知っておいて損はない Requires.jl について紹介します。

goropikari.hatenablog.com

環境

  • Julia 1.1.0
  • Requires.jl v0.5.2

三種の神器

Julia のパッケージを作るときに役に立つパッケージはいくつかありますが、私にとっての三種の神器は Revise.jl, PkgTemplates.jl, Requires.jl です。

github.com

github.com

github.com

前半2つはパッケージ製作者目線でないと困るもので、Requires.jl はパッケージの利用者目線で使ってくれないと困るものです。

Requires.jl

Requires.jl はパッケージを効率よく読み込むためのパッケージです。

例えば次のようなパッケージを作っていたとします。

module SamplePackage

using Plots

f(x) = ... # Plots に依存しない関数
g(x) = ... # Plots に依存しない関数

function h(x)
    Plots.plot(...)
end

end # module

ここで利用者は関数 f, g はよく使うけれど、可視化のための関数 h は滅多に使わないとしましょう。

ご存知の方も多いと思いますが Plots は Julia のパッケージの中でも重量級なので読み込みだけでも5秒近くかかります。仮にこのパッケージから Plots を抜いた場合の読み込み時間が 0.1秒だったとすると、利用者からするとろくに使いもしない関数のせいで読み込み速度が遅くされていると感じるでしょう。

Requires を使うと Plots を読み込む前は h は利用できないけれど、読み込んだら h が利用できるようになる、という風にすることができます。これにより無駄なパッケージの読み込みを抑えることができます。

使い方

公式の README を見ればわかると思いますが、オプション扱いにしたいパッケージを __init__() に入れていきます。

function __init__()
    @require パッケージ名 = "パッケージのUUID" (左で指定したパッケージが読み込まれたら実行される部分)
end

パッケージの UUID は General を直接見るか、]add でパッケージを追加したあと Project.toml を覗いてください。

github.com

書き方として重量級パッケージに依存する部分と依存しない部分をファイルで分けてしまうのが楽かなと個人的には思います。

# SamplePackage.jl
module SamplePackage

using Requires
include("noplot.jl")

function __init__()
    @require Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" include("plot.jl")
end

end # module
# noplot.jl
export hello

hello() = println("Hellow World!")
# plot.jl
using .Plots
export sinplot

function sinplot()
    println("Plot sin fn.")
    plot(sin)
end

ディレクトリ構成

SamplePackage/
├── Manifest.toml
├── Project.toml
└── src
    ├── noplot.jl
    ├── plot.jl
    └── SamplePackage.jl

実行例

julia> using SamplePackage

julia> hello()
Hellow World!

julia> sinplot()
ERROR: UndefVarError: sinplot not defined
Stacktrace:
 [1] top-level scope at none:0

julia> using Plots

julia> sinplot() # Plots を読み込むと使えるようになる。
Plot sin fn.

plot.jl 中の using .Plots. を付け忘れないように!完全に Plots をオプション扱いにしてパッケージの依存関係に入れたくない場合、. を忘れると以下のような警告が出ます。

julia> using Plots
┌ Warning: Package SamplePackage does not have Plots in its dependencies:
│ - If you have SamplePackage checked out for development and have
│   added Plots as a dependency but haven't updated your primary
│   environment's manifest file, try `Pkg.resolve()`.
│ - Otherwise you may need to report an issue with SamplePackage
└ Loading Plots into SamplePackage from project dependency, future warnings for SamplePackage are suppressed.

読み込み時間の比較

上記のパッケージと以下のパッケージの読み込み時間を比較してみます。

module SamplePackage2
export hello, sinplot

using Plots

hello() = println("Hellow World!")

function sinplot()
    println("Plot sin fn.")
    plot(sin)
end

end # module

読み込み時間

julia> @time using SamplePackage
  0.336427 seconds (455.48 k allocations: 24.660 MiB)

julia> @time using SamplePackage2
  5.265099 seconds (6.52 M allocations: 366.485 MiB, 5.65% gc time)

SamplePackage の方では Plots を読み込んでこないので読み込み時間を抑えることができました。

製作しているパッケージが計算が主でグラフに出すことはオプション機能とするならば、Requires.jl を使って Plots 依存の部分を分離すると利用者のストレスを減らすことができると思います。 他にも DifferentialEquations.jl などもヘビー級のパッケージですが、これらの重いパッケージに依存しない部分だけでも十分に利用価値があるならば、 Requires.jl を使って分離した方がユーザー思いのパッケージになると思います。