読者です 読者をやめる 読者になる 読者になる

りんごがでている

何か役に立つことを書きます

Julia現状確認 (言語編)

これはJuliaアドベントカレンダー1日目の記事です。2016年におけるJuliaの「現状」を確認していこうと思います。

Julia言語とは

Juliaとは一体どのような言語でしょうか。julialang.orgの最初の説明を用いると、「他の技術計算環境のユーザに馴染みのある構文を備えた、高レベル・高パフォーマンスな技術計算にための動的プログラミング言語」("a high-level, high-performance dynamic programming language for technical computing, with syntax that is familiar to users of other technical computing environments.")です。ここで言う「他の技術計算環境」とは、MATLABPythonのことを指していると考えられます。実際、Juliaの構文や関数名はMATLABとの類似性が高いため、Octaveのようなオープンソース版のMATLAB代替と捉えられることも多いようですが、プログラミング言語としてはかなり性質が異なります。また、Pythonとは文法や関数名の面ではそれほど類似性は無いように思いますが、似ている点も多数あります。

Juliaの最大の特徴を挙げるならば、動的プログラミング言語でありながらC言語などに匹敵するほど実行が高速であるという点でしょう。多くの技術計算では関数の呼び出しやループが頻繁に用いられますが、Juliaでは実行時コンパイル(JIT)のおかげでこれらの実行コストがネイティブコードにコンパイルするプログラミング言語並になっています。しかし、Juliaが行う実行時のコンパイルは、他の言語でよくみられるJITコンパイラとは性質を異にするようです。PyPy(PythonJITコンパイラ実装)やLuaJIT(LuaJITコンパイラ実装)はTracing JITと呼ばれるもので、実行時にプロファイルを行ってホットスポットを同定し、実行時の情報を集め、コンパイルと最適化を行います。しかし、JuliaのJITコンパイラはコードの実行前に型推論と最適化を行い、その後コードを実行します。型推論の結果、型が決定できる部分では強力な最適化を行い、曖昧性が残る部分では実行時の型に合わせて実行できるようにしています。

Julia言語の現状

Julia言語の実装であるJulia処理系はすべてGitHub上で開発されています。レポジトリはこちら( https://github.com/JuliaLang/julia)です。2016年12月1日現在の最新のリリース版はv0.5.0で、9月20日頃リリースされたものです。次期バージョンはv0.6でほぼ間違いないと思います。気になるのはv1.0のリリース時期ですが、今年のJuliaConでJuliaの生みの親のひとりであるStefan Karpinski氏が宣言したことによると、Julia 1.0のリリースは2017年を予定しているようです。次期バージョンのv0.6はv1.0のα版的な意味合いで開発されているようなので、v0.6が出てすぐv1.0がリリースされるものと思われます。しかし実は、v1.0が出てしばらく1.0のままということはなく、1-2年のうちにv2.0も出すつもりのようです。

振り返って、現行のv0.5ではどのような変更があったのかを確認してみます。まず性能面で大きな影響があったのが、クロージャが高速になったことです。v0.4までのJuliaでは、関数がすべて一つの型にまとめられており最適化の恩恵を受けにくかったのですが、v0.5からは関数が別々の型を持つようになりました。これによって、無名関数などの関数の呼び出しが高速化され、安心して使えるようになりました。例えば、map(x -> 2x, array)のようなコードがforループと同等の速度が出るようになります。また、ASCIIとUTF-8に分かれていた文字列型がString型に統一され、内部のエンコーディングを気にせず使えるようになりました。これで、Julia内では常にUTF-8エンコーディングのみを考慮すればよく、プログラムを書くのが大変楽になりました。さらに、新機能として、実験的にマルチスレッディングのサポートが入りました。今のところ、Julia内部でまだマルチスレッディングに対応していない部分(例えばIOなど)があるので、常に問題なく使えるわけではありませんが、競合などがない場合にはforループに@threadsをつけるだけで中身を並列に実行してくれます。これらの変更の他にも、generator式(i for i in 1:10などが式として受け渡しできる)やfusing構文(sin.(cos.(x))などがベクトルxに対して一時配列を作らない)などの新しい機能が導入されています。

では、v0.6はどうなるでしょうか。v0.6はv1.0のリリースのためにいつもより早めにfeature freezeが入るようですので、そろそろ新機能や変更点などが出揃うと思います。その中で、おそらく一番大きな変更が型システムの変更になるでしょう。実装はここ(https://github.com/JuliaLang/julia/pull/18457)にプルリクがあるので、すぐに試すことができます。これを少し詳しく説明してみます。

JuliaにはSetと呼ばれる集合を表す型があります。これは型パラメータを取ることができる型で型パラメータをTとすると、Set{T}と書けます。つまりSet{T}T型の要素を持つ集合です。このとき型パラメータTはどのような型でも良いのですが(注: 実行時にはhashを実装しているなどの制約がある)、逆にTに何らかの制約(T<:Integerなど)を付けた型は作ることができませんでした。しかし、次期バージョンのJuliaの型システムでは、Set{T} where T <: IntegerなどのようにT型に制約をつけることができます。以下の例では、型制約がない場合とある場合でどのようにサブタイピングの結果が異なるかを示しています。

julia> Set{T} where T  # 型制約なし
Set{T} where T

julia> Set{Int} <: (Set{T} where T)
true

julia> Set{Float64} <: (Set{T} where T)
true

julia> Set{T} where T <: Integer  # 型制約あり
Set{T} where T<:Integer

julia> Int <: Integer
true

julia> Set{Int} <: (Set{T} where T <: Integer)
true

julia> Float64 <: Integer
false

julia> Set{Float64} <: (Set{T} where T <: Integer)
false

サブタイピングに使えるということは、多重ディスパッチにも使えるということです。実際、以下の様な引数の型も書けます。

julia> f(xs::(Tuple{V,V} where V <: AbstractVector{T}), x::T) where T <: Integer = (xs[1] + x, xs[2] - x)
f (generic function with 1 method)

julia> f(([1,2,3], [1,2,3]), 2)
([3,4,5],[-1,0,1])

これが実際どのように役立つのかは、実例が無いので私にもまだよく分かっていません。また、記法に関してはまだ変更が入るかもしれません。

もうひとつ重要な新機能は、依存する関数の自動更新です。Julia v0.5では以下のように関数gを実行した後fを更新してもfの変更は反映されませんでした。

julia> f(x) = x + 1
f (generic function with 1 method)

julia> g(x) = f(2x)
g (generic function with 1 method)

julia> g(1)
3

julia> f(x) = x - 1
WARNING: Method definition f(Any) in module Main at REPL[1]:1 overwritten at REPL[4]:1.
f (generic function with 1 method)

julia> g(1)
3

これでは、開発中など頻繁に関数を書き換えるときにいちいち再起動する必要があります。Juliaはコードをコンパイルするためこのような動的な変更は実装不可能だと私は思っていましたが、現在進行中の変更(https://github.com/JuliaLang/julia/pull/17057)を取り入れると、fの変更が正しくgの実行に反映されるようになります。

julia> f(x) = x + 1
f (generic function with 1 method)

julia> g(x) = f(2x)
g (generic function with 1 method)

julia> g(1)
3

julia> f(x) = x - 1
WARNING: Method definition f(Any) in module Main at REPL[1]:1 overwritten at REPL[4]:1.
f (generic function with 2 methods)

julia> g(1)
1

大変不思議です。どうやらworldという利用可能なメソッドをチェックできるようにする機構が組み込まれているようなのですが、私には詳しく分かりません。もし何か分かりましたら、また記事にしようと思います。

この記事ではJulia言語の現状を簡単にまとめてみました。Julia言語以外のコミュニティの動向などは、他の機会にまたご紹介しようと思います。明日は、Ishitaさんの担当です。