りんごがでている

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

Julia言語の0.5の変更点

9月20日にJulia言語の最新版である0.5がリリースされました。Juliaのメーリングリストに投稿されたアナウンスメントはこちらです: https://groups.google.com/d/msg/julia-users/J2DiH1GnM8o/aO2Ku8o-CgAJ

きっと近いうちに本家のブログで詳しい変更点の紹介があると思いますが、私のブログでも一足先に主要な変更点をご紹介しようと思います。

クロージャの効率化

とりわけ重要な変更点として挙げられるのがJuliaのクロージャが効率化されたことです。 0.4までのJuliaでは、Juliaの関数に関数を渡したりJuliaで関数を返すような関数を作ると、その実効速度が極めて遅いことが問題でした。 これは、すべての関数が Functionという型にまとめられていたせいで、Juliaのコンパイラが特化したコードを吐けないせいでした。 これが0.5では関数は各々自分専用の型を持つように変更されたため、クロージャを使ったコードでも遅くなるということが無くなりました。

関数を受け取る関数の例としてよく上げられるのがmap関数でしょう。 以下の簡単なベンチマークを見てもJulia 0.5では0.4に比べて10倍ほど速度が向上しています。

Julia 0.5.0:

julia> f = x -> 2x
(::#1) (generic function with 1 method)

julia> x = rand(100000);

julia> map(f, x);

julia> @time map(f, x);
  0.000572 seconds (134 allocations: 789.078 KB)

Julia 0.4.6:

julia> f = x -> 2x
(anonymous function)

julia> x = rand(100000);

julia> map(f, x);

jjulia> @time map(f, x);
  0.005628 seconds (200.01 k allocations: 3.815 MB)

クロージャを使うようなプログラムではこれでデザインの幅が広がり、今まで実行効率の観点からできなかったコードが書けるようになります。

ジェネレータ式

ジェネレータ式という新しい式が0.5から加わりました。 これは、イテレータをラップして式にしたようなもので、値を次々生成することができる式です。 簡単な例では、 2x for x in 1:9 のようなものが普通の値として扱えるようになりました。

julia> g = (2x for x in 1:9)
Base.Generator{UnitRange{Int64},##5#6}(#5,1:9)

julia> collect(g)
9-element Array{Int64,1}:
  2
  4
  6
  8
 10
 12
 14
 16
 18

もちろん関数はこのジェネレータを値として受け取ることもできるため、次のような計算もできます。

julia> sum(g)
90

また、ifで値をフィルターすることも可能です。

julia> h = (2x for x in 1:9 if isodd(x))
Base.Generator{Filter{Base.#isodd,UnitRange{Int64}},##7#8}(#7,Filter{Base.#isod
d,UnitRange{Int64}}(isodd,1:9))

julia> collect(h)
5-element Array{Int64,1}:
  2
  6
 10
 14
 18

julia> sum(h)
50

文字列型の統合

Julia 0.4の標準ライブラリに大量にあった文字列型が整理され、0.5では主にString型とSubString型のみになりました。 String型は従来のUTF8String型に当たるもので、ユニコード文字列を表現できます。SubString型は従来からありましたが、これはString型の一部分を切り出す際に使われています。 将来的にはSubString型もString型に統合される予定のようですが、0.5時点では0.4のASCIIStringUTF8StringString型に統合されたと見るのが良さそうです。

Julia 0.5.0:

julia> typeof("foo")
String

julia> typeof("いえい")
String

Julia 0.4.6:

julia> typeof("foo")
ASCIIString

julia> typeof("いえい")
UTF8String

昔の文字列型はLegacyStrings.jlパッケージに移動され、UTF-16などのエンコーディング方式はStringEncodings.jlパッケージで新たにサポートされるようです。

Fused broadcasting構文

Fused broadcasting構文は、Juliaのベクトル計算を効率化する新しい構文の拡張です。 0.4までのJuliaでは、配列に対して何度も関数適用を行うとその都度新しい配列が生成されていました。 新しいJuliaでは、これが構文レベルで融合(fuse)されるようになります。

sin.(cos.(x))という式があったとしましょう。 0.4ではまずcos.(x)が評価され、xの各要素にcos関数を適用した新しい配列が作られます。 続いてその各要素にsin関数が適用され、また新しい配列が作られ、先ほどの配列は将来的にGCにより破棄されます。 0.5では、この式はまず broadcast(x -> sin(cos(x)), x)のような式に変換されます。 これは、xから値を一つずつ取り出してcossinを順に適用するため、一時的な配列の生成が起きず、一気に2つの関数を適用した配列が生成されます。

さらに、x .= ...のような構文もbroadcast!(identity, x, ...)に変換されるので配列への代入がin-placeに行うことができるようになりました。

マルチスレッドのサポート

マルチスレッドを使った計算の並列化が新たにサポートされました。 今のところ、@threadsマクロを使ってfor文の並列化を行うことができます。 例えば、以下のように複数のタスクを並列に処理することができます。

using Base.Threads

function dotasks(tasks)
    @threads for i in 1:endof(tasks)
        dotask(tasks[i])
    end
end

配列などを領域で分割しておけば配列に対する処理の並列化もできますし、私の作ったBGZFStreams.jlではgzipの解凍処理をこのマルチスレッド機能を使って並列化しています。

テストフレームワークの強化

Julia標準のテストフレームワークがより強化され、テストをきれいに構造化できるようになりました。 今までBase.Testから提供されていた機能では複数のテストをまとめる機能がなく、フラットにすべてのテストを書き連ねるしか方法がありませんでした。 しかし0.5からは@testsetマクロが加わり、次のようにテストを構造化することができるようになりました。

@testset "sum" begin
    @test sum([1,2,3]) == 6
    @test sum([1.0, 2.0, 3.0]) == 6.0
end

その他の変更点

0.5では内部的なものも含め多数の変更がなされています。そのすべてはJuliaのリリースノートから参照することができますので、気になる方は是非調べてみて下さい。