Juliaのfor文はなぜ速い?
1年以上ぶりのブログ更新ですね。
atsuoishimotoさんのブログ記事で,私のブログ記事が言及されていました。 atsuoishimoto.hatenablog.com
書いてある内容はまさにその通りで,for文を回すこと自体が取り立てて遅いわけではなく,その中で何らかの処理をする際に動的型付け言語であるPythonの性質上避けて通れない処理があり,その処理をfor文の中で何度も繰り返すことで結果としてfor文全体の処理が遅くなるという内容でした。
そうすると,ひとつの疑問として,なぜ同じ動的型付け言語であるはずのJulia言語では同様の処理が高速なのかが気になるところでしょう。この記事ではそのあたりを少し解説しようと思います。
Juliaの型推論
問題となっているコードは,以下のように2つのベクトルの内積を計算するコードです。
function dot(a, b) s = zero(eltype(a)) for i in 1:endof(a) s += a[i] * b[i] end return s end
このコードは,特に型の指定などがないにも関わらず,Pythonで似たコードを書いた場合と比べて非常に高速に動作します。
Julia:
julia> a = ones(100000); b = ones(100000); julia> @benchmark dot(a, b) BenchmarkTools.Trial: memory estimate: 16 bytes allocs estimate: 1 -------------- minimum time: 121.605 μs (0.00% GC) median time: 121.666 μs (0.00% GC) mean time: 132.996 μs (0.00% GC) maximum time: 1.456 ms (0.00% GC) -------------- samples: 10000 evals/sample: 1
In [2]: %timeit dot(a, b) 31.4 ms ± 680 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
ここでPythonがこれほど遅くなる理由は,元のブログ記事で解説されているとおり,for文の中で行われている一つ一つの計算の裏で様々な付随する処理が行われているためです。例えば,乗算を行う*
演算子では,そもそも被演算子の型が乗算をサポートしているかなどの確認が必要です。動的言語では変数の型が動的に決定されるため,こうした処理が付随するのは避けて通れないようにも思えますが,Juliaはこうした処理を省略することで高速化を達成しています。
Juliaはなぜこうした処理を省略できるのでしょうか? その答えはJuliaの型推論にあります。実は,Juliaの処理系は関数を実行する前に型推論を行います。Juliaの型推論器は,関数のコードを見て回って,型が決定できるところに自動的に型注釈を付けて回ります。先ほどのコードを例に少し説明してみましょう。このコード例では,dot(a, b)
という呼び出しが起きた際に引数であるa
とb
の型が決定します。上の例では両方ともVector{Float64}
(これはArray{Float64,1}
の別名) というFloat64
型 (倍精度の浮動小数点数の型) を要素とする1次元配列です。こうすると当然for文の中にあるa[i]
やb[i]
はFloat64
型の値を取り出すことが分かります。すると,a[i] * b[i]
という配列のi
番目の要素を取り出して積を計算する処理は,Float64
同士の積だと分かりますので,被演算子が乗算をサポートしているかどうかをいちいちfor文の内部で確認する必要はありません。こうした処理を勝手に行ってくれるのが型推論です。
型推論の結果を見てみましょう。@code_typed
というマクロを使うと,関数の型推論の結果を表示することができます。
julia> @code_typed dot(a, b) CodeInfo(:(begin s = (Base.sitofp)(Float64, 0)::Float64 # line 3: $(Expr(:inbounds, false)) # 省略 SSAValue(4) = (Base.arraysize)(a, 1)::Int64 # 省略 $(Expr(:inbounds, :pop)) SSAValue(5) = (Base.select_value)((Base.slt_int)(SSAValue(4), 0)::Bool, 0, SSAValue(4))::Int64 SSAValue(6) = (Base.select_value)((Base.sle_int)(1, SSAValue(5))::Bool, SSAValue(5), (Base.sub_int)(1, 1)::Int64)::Int64 #temp# = 1 17: unless (Base.not_int)((#temp# === (Base.add_int)(SSAValue(6), 1)::Int64)::Bool)::Bool goto 27 SSAValue(7) = #temp# SSAValue(8) = (Base.add_int)(#temp#, 1)::Int64 i = SSAValue(7) #temp# = SSAValue(8) # line 4: s = (Base.add_float)(s, (Base.mul_float)((Base.arrayref)(a, i)::Float64, (Base.arrayref)(b, i)::Float64)::Float64)::Float64 25: goto 17 27: # line 6: return s end))=>Float64
下の方を見てみると,(Base.mul_float)((Base.arrayref)(a, i)::Float64, (Base.arrayref)(b, i)::Float64)::Float64
と書かれた部分があるのがわかると思います。この部分がまさにa[i] * b[i]
の計算にに対応する部分で,それぞれの被演算子と演算結果がFloat64
になることがちゃんと推論できています。また,一番最後に=>Float64
と書かれているように,dot(a, b)
の計算結果がFloat64
型であることも推論されています。計算結果の型も推論できることで,他の関数からdot
関数を呼び出したときもその計算結果が推論でき,型の情報がどんどん伝播していきます。
型推論は,ソースコードの上でインタープリタを実行するように行います。このインタープリタは引数の値などを扱うインタープリタではなく,型を扱うインタープリタです。型推論の詳しいアルゴリズムは実装を読むか,Inference Convergence Algorithm in Juliaと Inference Convergence Algorithm in Julia - Revisitedなどの解説を参照して下さい。
コードの生成
ソースコードに現れる変数や関数の返り値の型が型推論で判明すると,いくつかの確認処理を省くだけでなく,Juliaはその型に特化した命令を生成することができます。関数の実行時にはコード生成を行うことで処理速度を上げています。その中間状態を覗く@code_llvm dot(a, b)
というマクロがありますので,結果を見てみましょう。
julia> @code_llvm dot(a, b) define double @julia_dot_62600(i8** dereferenceable(40), i8** dereferenceable(40)) #0 !dbg !5 { # 省略 idxend2: ; preds = %idxend %18 = add i64 %"#temp#.08", 1 %19 = getelementptr double, double* %10, i64 %13 %20 = load double, double* %19, align 8 %21 = getelementptr double, double* %12, i64 %13 %22 = load double, double* %21, align 8 %23 = fmul double %20, %22 %24 = fadd double %s.09, %23 %25 = icmp eq i64 %"#temp#.08", %4 br i1 %25, label %L27.loopexit, label %if }
この結果はLLVM IRという言語で書かれています。LLVM IRは機械語に近い低レベルな命令列で,C言語などのコンパイラであるclangやRustのバックエンドでも使われているものです。
この結果を見ると%20 = load double, double* %19, align 8
という命令でメモリのある部分からdouble
の値を読み出しているのがわかります。これはちょうどa[i]
(もしくはb[i]
)に相当する処理です。また,読み出した値の積を計算する%23 = fmul double %20, %22
という命令があるのも分かります。fmul
という命令は浮動小数点数の積を計算するのに特化した命令です ('fmul' Instruction)。ではa
やb
の要素がFloat64
でなく整数型のInt64
だったらどうなるでしょうか? そういう場合でもJuliaは型推論を行ってInt64
型に特化した積の演算命令を生成します。このように型推論の結果を使ってある処理に特化した命令を使うことで,非常に高効率なコードを生成します。ループ1回あたりでは大した違いはありませんが,長く回すループだと積もり積もって大きな差になります。こうして生成された関数のコードは,次回以降に同じ型の引数がくると再利用されます。
副作用
Juliaは関数の実行時に型推論とコード生成を行うことを説明しましたが,これには副作用もあります。初めて関数を呼び出す際に型推論とコード生成・最適化にかかるコストが上乗せされます。このコストがペイするほどのものかどうかを確認すべきなのでしょうが,現在のJulia (v0.6.2)は特殊なことをしない限り必ず型推論とコード生成を行います。ですので,初回の関数呼び出しでワンテンポ遅れたり,それが巨大なパッケージになると積もり積もって数秒から数十秒の遅れになったりします。とても良い例えをTwitterで見つけたので引用しようと思います。
これは人間と車が一メートル競走でどっちが早いかを見てるのと同じことだとおもいます.
— ごまふあざらし (@MathSorcerer) 2018年1月7日
将来的にもっとJuliaの処理系が賢くなれば,自分が今1メートル走をしようとしてるのかフルマラソンをしようとしてるのか分かるようになり,こうした副作用の効果も軽減されるようになると思います。
Pythonで同じことができないのか
Numbaというプロジェクトは,Juliaに似たような型推論とコード生成を行っているようです。
使い方は簡単で,@jit
というアノテーションを関数につけるだけです。
import numba @numba.jit def dot(a, b): s = 0.0 for i in range(len(a)): s += a[i] * b[i] return s
実際にこれを試してみると,Juliaと同じレベルの高速なコードになりました。
In [2]: %timeit dot(a, b) 133 µs ± 1.43 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
どの程度複雑なコードまで上手く行くのかは分かりません(例えば,ユーザ定義型でもちゃんと最適化できるのか)が,非常に面白いプロジェクトだと思います。今後,RやRubyでも似たようなアプローチが出てくるかもしれませんね。
EzXML.jlを作った話
先月ですが、EzXML.jlというXMLやHTMLを扱うためのパッケージをリリースしました。 EzXML.jlはlibxml2というC言語で書かれたメジャーなライブラリのラッパーなのですが、それをJuliaでラップする過程で色々と工夫がありましたので、その辺を共有したいと思います。また、パッケージの開発についても私の思うところを色々と紹介していきたいとも居ますので、これからJuliaのパッケージを作ってみようと思う方には参考になるかと思います。
何故作ったか
そもそも、JuliaにはLightXML.jlというパッケージがあり、こちらもlibxml2をラップしたパッケージでEzXML.jlと重複する部分がかなりあります。LightXML.jlはJuliaのXMLを扱うパッケージの中では最もよく使われているもので、多くの依存パッケージがあります。しかしながら、いくつか問題があり、私はこのパッケージにパッチを送るでもなく新しいパッケージを作ることにしました。
LightXML.jlで問題だと思われたものを列挙すると主に以下の様なものが挙げられます。 * APIがJuliaっぽくない * 型の設計があまりうまくない * 機能が少ない * C側で確保されたメモリーを自動的に解放してくれない
まず、APIに関してですが妙な名前の関数が提供されています。例えば、XMLファイルをパースして読み込む関数はparse_file
という何をするのか分からない名前です。Juliaではしょっちゅうusing
を使ってパッケージから提供されている関数をパッケージ名を付けずに利用しますから、例えばちょっと大きめのプログラムでparse_file
という関数が呼ばれていても、これがXMLを読み込む関数だとはなかなか思えません。また、幾つかの関数名にはchild_nodes
やis_elementnode
などアンダースコアが使われていて、これもJuliaっぽい関数の命名法ではないです。例えば、Juliaの標準関数を見てみるとeachline
やhaskey
などアンダースコアを使わない関数名が一般的で、スタイルガイドにもそのように記述されています。
型に関しても、ちょっと設計がよくない気がします。XMLのDOMモデルでは、XMLの文書中を構成する要素をノードと呼び、<element/>
のような要素ノードや<!-- comment -->
のようなコメントノードなど様々な種類のノードがあるのですが、LightXML.jlではそのようなノードを別々のJuliaの型でラップしています。ですので、以下のようにノードを見つけるとその種類を判別して目的の型のノードのキャストする必要があります。
for c in child_nodes(xroot) if is_elementnode(c) e = XMLElement(c) # ... end end
これはいかにも面倒くさいですし、一度生成したオブジェクトを別のオブジェクトへキャストすることで余計なオブジェクトの生成も起きますから、パフォーマンス上もあまりよろしくないと思います。
また、libxml2は名前空間やXPathなど様々な機能を実装しているのですが、LightXML.jlから提供されているのはそのごく一部でXMLの読み書きとDOM操作だけです。それに、libxml2を通して確保したメモリー領域は、Julia側からの参照がなくなっても自動的には解放してくれないので、自分で解放しないとメモリーリークを起こします。
これらの問題を解決して快適にXMLを扱えるようにするため、EzXML.jlという新しいパッケージを開発することに決めました。
EzXML.jlの売り
EzXML.jlの売りは、その名前の通り簡単に(easy)にXMLを扱えるようにすることです。そのため、APIや型の設計をもう一度考え直し、かつlibxml2が提供する便利な機能を使えるようにしています。次のコードは、EzXML.jlを使ってXMLのツリーを辿ったり、XPathを使うデモです。
using EzXML # Parse an XML string # (use `readxml(<filename>)` to read a document from a file). doc = parsexml(""" <primates> <genus name="Homo"> <species name="sapiens">Human</species> </genus> <genus name="Pan"> <species name="paniscus">Bonobo</species> <species name="troglodytes">Chimpanzee</species> </genus> </primates> """) # Get the root element from `doc`. primates = root(doc) # Iterate over child elements. for genus in eachelement(primates) # Get an attribute value by name. genus_name = genus["name"] println("- ", genus_name) for species in eachelement(genus) # Get the content within an element. species_name = content(species) println(" └ ", species["name"], " (", species_name, ")") end end println() # Find texts using XPath query. for species_name in content.(find(primates, "//species/text()")) println("- ", species_name) end
EzXML.jlがXMLファイルを読み込むときの関数名はreadxml
です。これは、標準ライブラリにもあるreadcsv
に沿って命名したものです。read
という関数名はJuliaの標準ライブラリではファイルから読み込む際に使われる一般的な動詞ですので、その点でも一貫性があります。文字列からデータを読み込む場合にはparsexml
という関数名になっていて、これもJuliaではparse
が文字列に対して頻繁に使われることを反映されています。ある要素の属性を取るには辞書(Dict
)と同じようにelement[name]
という構文が使えます。値をセットするにはもちろんelement[name] = value
と書きます。他の関数に関しても、一貫性があって一度理解すればあまりレファレンスを引かなくてもプログラミングできるようになっています。
型の設計に関してもなるべくシンプルになるようにしています。EzXML.jlが提供している型は主にEzXML.Document
とEzXML.Node
の2種類だけです。EzXML.Document
はXML文書全体に対応する型で、EzXML.Node
はその構成要素であるノードを表す型です。EzXML.Node
はXML中のノードっぽいものすべてをラップしますので、要素も、テキストも、コメントも、属性もすべてがEzXML.Node
です。そのノードがどのような種類のノードか知るにはnodetype
関数を呼びます。これらの型の名前はコード中で使われることはあまりないですから、パッケージとしてはexportしていません。しかし、必要であれば上のようにモジュール名を付けて使うこともできます。
機能もLightXML.jlより豊富です。名前空間を扱うこともできますし、XPathを使って目的の要素を検索することもできます。XPathはfind
という関数をオーバーロードしていて、find(node, "//foo")
とすればnode
ノード以下のfoo
要素をすべて取得することができます。また、メモリに乗り切らない大規模なXMLファイルを高速にパースするために、streaming readerも提供しています。
最後に、EzXML.jlはメモリリソースを自動的に解放してくれます。普通にJuliaを使っている人はメモリリソースの解放なんて気にしたくないでしょうから、自動的にやってくれれば大変楽です。なので、EzXML.jlではこの辺の面倒を見てあげることにしています。この次では、この実装がどのようになっているのかを説明していきます。
内部実装
まずはJuliaでCのライブラリーをラップする際の基本的なことを確認しておきましょう。JuliaのオブジェクトとC言語の構造体ではメモリレイアウトに互換性があります。例えば、struct point_s { int kind; double x; double y; }
という構造体があれば、Julia側からはimmutable Point; kind::Cint; x::Float64; y::Float64; end
のような型のオブジェクトを定義してやれば、そのメモリレイアウトをそのままJulia側に写すことができます。Cの関数からpoint_s*
のようなポインタ値が返された場合は、Julia側でunsafe_load(ptr::Ptr{Point})
を使ってJuliaのPoint
オブジェクトに変換できます。また、C言語の関数の呼び出しはccall
で実現できます。
EzXML.jlでは、libxml2のノードを扱う時は以下の_Node
型に写しています。これは、libxml2のどんな種類のノードも共通して持っているフィールドです。後で述べますが、最初の_private
フィールドが重要です。
immutable _Node _private::Ptr{Void} typ::Cint name::Cstring children::Ptr{_Node} last::Ptr{_Node} parent::Ptr{_Node} next::Ptr{_Node} prev::Ptr{_Node} doc::Ptr{_Node} end
Juliaはオブジェクトが解放されるときに関数を呼び出す仕組み(finalizer
関数)を提供しており、これを使うことでリソースの解放ができます。しかしこれは、想像するよりちょっと複雑です。何故なら、XMLのノードはlibxml2内で繋がっていますから、誤ってつながっている一部のノードを勝手に解放してしまうと、一貫性が保たれなくなってしまいます。以下のコードを見てみましょう。
function extract_first_child(filename) doc = readxml(filename) r = root(doc) c1 = firstchild(r) return c1 end # foo.xml: # <root> # <child1/> # <child2/> # </root> child = extract_first_child("foo.xml")
この関数ではXML文書中の最初の要素を返しています。関数が返った後はJuliaレベルではdoc
への参照が無くなりますから、Juliaのガーベッジコレクション(GC)はdoc
を解放しようとします。このときにlibxml2のメモリーも解放して良さそうに思えますが、実際にはそうではありません。doc
が参照しているlibxml2の構造体はr
を通してc1
につながっていて、c1
もdoc
へのポインターを持っています。ですので、領域を解放するには、XMLのツリーのどこにもJuliaからの参照が残っていない状態である必要があります。
この問題を回避するために、EzXML.jlではメモリーの解放は常に特定のノードから起きるようにしています。実装上は、Node
型のオブジェクトはptr
というlibxml2のノードを表す構造体へのポインターと自身を管理しているowner
という2つのフィールドを持っています。このowner
オブジェクトが他のノードのメモリー管理を担っているオブジェクトです。
type Node ptr::Ptr{_Node} owner::Node end
Node
のコンストラクターの内部では、以下の様なコードを使って最上位のNode
のポインターを手繰り寄せて、そのノードのowner
に指定しています。つまり、オーナーはXMLツリーの最上位にあるノードがその役割を果たします。
# determine the owner of this node owner_ptr = ptr while unsafe_load(owner_ptr).parent != C_NULL owner_ptr = unsafe_load(owner_ptr).parent end
このowner
フィールドを持つ理由は他にもあります。owner
を持つことで、GCがその子(子孫)ノードより先にオーナーを解放しようとするのを防ぐことができます。上の関数の例で言えば、c1
がdoc
への参照をJuliaのレベルで保持しているため、c1
がある限りdoc
が先に解放されることはありません。さらに途中に挟まれているr
は自身や他のノードのオーナーではないので、Juliaはr
をGCするときにlibxml2で確保されたメモリー領域を解放しません。こうして、常にノードは最上位のノードへの参照を持ち、最上位のノードがメモリーを解放することにして、誤ってGCされるのを防いでいます。
しかし実は、もうひとつ考えなければいけない問題があります。それは、Julia側からあるlibxml2のノードに対して複数の参照を作ってしまうことです。以下の様な簡単なコードを考えてみると、すぐに問題が分かります。
doc1 = readxml("foo.xml")
doc2 = document(root(doc1))
このとき、単純に実装するとdoc1
とdoc2
はlibxml2の同じノードを指しているのに別々のJuliaオブジェクトになっています。すると、あるノードの対して2つのオーナーが存在することになり、両者が最終的に同じノードを解放しようとして後のほうが不正な操作になります。
この問題はlibxml2のノードの構造体にJuliaオブジェクトへのポインターを保持することで解決しています。既に述べたように、libxml2のノードの構造体には_private
と呼ばれるフィールドがあり、ユーザーが自由に使うことができます。そこで、このフィールドにJuliaのNode
オブジェクトに対するポインターを保持しておき、あるノードに対するJulia側のNode
オブジェクトを作る際には、もしあればここからオブジェクトを取り出しています。つまり、既にJulia側であるノードに対するオブジェクトを作成していればそれを使い、無ければ新しく作ってポインターをノードの保持しておくという操作をNode
のコンストラクター内で行っています。こうすることで、あるlibxml2のノードに対するJulia側のNode
オブジェクトは常に高々1個になり、二重に領域を解放してしまうことを防ぐことができます。
最終的に、Node
のコンストラクターは以下の様な感じになります(実際のコードを少し簡略化しています)。
CのポインターからJuliaのオブジェクトを取り出すのがunsafe_pointer_to_objref
で、Juliaのオブジェクトのポインターを取得するのがpointer_from_objref
です。
function Node(ptr::Ptr{_Node}) # return a preallocated proxy object if any str = unsafe_load(ptr) if str._private != C_NULL # found a valid proxy return unsafe_pointer_to_objref(str._private)::Node end # find the owner of this node owner_ptr = ptr while unsafe_load(owner_ptr).parent != C_NULL owner_ptr = unsafe_load(owner_ptr).parent end if ptr == owner_ptr # manage itself node = new(ptr) node.owner = node else # delegate management to its owner owner = Node(owner_ptr) node = new(ptr, owner) end # register finalizer and store the pointer to a node object finalizer(node, finalize_node) unsafe_store!(convert(Ptr{UInt}, ptr), convert(UInt, pointer_from_objref(node))) return node end
finalize_node
は自身がオーナーである場合は、それ以下の管理するノードにつながっているJuliaのオブジェクトを切り離し、libxml2の関数を呼んでノードを解放しています。もしオーナーでなければ、_private
にNULL
ポインターを代入して、JuliaのオブジェクトがGCされてもう存在しないことを示しています。
# Finalize a Node object. function finalize_node(node) node_ptr = node.ptr if node === node.owner # detach pointers to C structs of descendant nodes traverse_tree(node_ptr) do ptr str = unsafe_load(ptr) if has_proxy(str) # detach! unsafe_extract_proxy(str).ptr = C_NULL end end # free the descendants if unsafe_load(node_ptr).typ == DOCUMENT_NODE ccall((:xmlFreeDoc, libxml2), Void, (Ptr{Void},), node_ptr) else ccall((:xmlFreeNode, libxml2), Void, (Ptr{Void},), node_ptr) end elseif node_ptr != C_NULL # indicate the proxy does not exit anymore store_proxy_pointer!(node, C_NULL) end return nothing end
以上で、EzXML.jlの大まかな内部実装がお分かりになるかと思います。実際には、サブツリーを別のツリーへと動かす際にはオーナーをアップデートする必要があるなど細かい処理が多少あるのですが、そのへんまで興味ある方は元のコードを読んでみて下さい。まだ新しいパッケージですので、使ってみてバグや提案などがある場合は報告して下さい。
RユーザーのためのJulia100問100答
R Adevnt Calendar 8日目の記事です。大幅に遅れて大変申し訳ないです。
この記事ではR言語ユーザーのために100問100答形式でJuliaを紹介していこうと思います。
Julia言語
Juliaってどういう言語なの?
Juliaは高レベルでハイパフォーマンスな技術計算のための動的言語だよ。書きやすさと実行速度の両立がウリの言語だよ。
誰が作ってるの?
主にボストンのMITの人達が中心に作っている言語だよ。特にJeff Bezonson, Stefan Karpinski, Viral Shah, Alan Edelmanの4人が初期の重要人物だよ。
自由に使えるの?
Juliaの処理系はMITライセンスで配布されているから、商用でもなんでもかなり自由に使えるよ。
どれくらい速いの?
すごく速いよ!大体C言語の2倍以内くらいの収まる速度だよ。
Rと比較してどうなの?
数倍から数百倍くらい高速かな。特にループや関数呼び出しがたくさんある場合には顕著な差が出るよ。
公式サイトにあるベンチマークの抜粋を載せておくよ (C言語=1.0)。
実例はある?
forループがあるような場合はかなりはっきり差が出るよ。
例えば1からNまでのコラッツ予想を実証する関数collatz
をJuliaとRで書いてみたよ。
function collatz(N) for n in 1:N while n != 1 if iseven(n) n = div(n, 2) else n = 3n + 1 end end end end
collatz <- function (N) { for (n in 1:N) { while (n != 1) { if (n %% 2 == 0) { n = n %/% 2 } else { n = 3 * n + 1 } } } }
N=100,000で試してみるとJuliaはRの300倍ぐらい高速だったよ。
julia> @time collatz(100000)
0.026903 seconds (4 allocations: 160 bytes)
R> system.time(collatz(100000))
user system elapsed
8.429 0.028 8.479
どうして速いの?
LLVMというコンパイラの基盤ライブラリをつかって実行時にコンパイルを行っているからだよ。あとJulia言語自体が最初からパフォーマンスを考慮して設計されているよ。詳しくはBezansonら論文を参照してね。
どこが技術計算に向いてるの?
実行速度が速く、かつ動的プログラミング言語で気軽に実行できるから短いスクリプトで計算するのに向いてるよ。あと、数値計算でよく用いられるデータ型や関数が最初から用意されているから、インストールしてすぐ使い始められるよ。例えば、線形代数のためにBLASやLAPACKの関数が標準で入ってるよ。
Juliaのレポジトリはどこ?
GitHubのレポジトリがここにあるよ: https://github.com/JuliaLang/julia
インストール用のバイナリは?
公式サイトのダウンロードページにWindows, Mac, Linux用のが用意してあるよ: http://julialang.org/downloads/
Juliaのマニュアルやチュートリアルは?
Juliaはドキュメントがよく書かれているから、公式マニュアルを読むのがオススメだよ。 日本語だと私が書いたJuliaのチュートリアルやM.Hiroiさんのサイトがあるよ。
環境
どうやって実行するの?
次のスクリプトをhello.jlとしてファイルに保存したとしよう。
println("hello, world")
Juliaをインストールしてあるならjulia
コマンドがあるはずなので、これにさっきのファイルを渡せばRscript
みたいに実行できるよ。
$ julia hello.jl
hello, world
対話セッション(REPL)を使うには、引数無しでjulia
を実行しよう。そうすると、下のようにRみたいなJuliaのREPLが立ち上がるよ!
$ julia
_
_ _ _(_)_ | A fresh approach to technical computing
(_) | (_) (_) | Documentation: http://docs.julialang.org
_ _ _| |_ __ _ | Type "?help" for help.
| | | | | | |/ _` | |
| | |_| | | | (_| | | Version 0.5.0 (2016-09-19 18:14 UTC)
_/ |\__'_|_|_|\__'_| |
|__/ | x86_64-apple-darwin14.5.0
julia> 1 + 2
3
julia>
ヘルプの調べ方は?
RみたいにREPLに?
と打ってみよう。そうするとプロンプトがhelp?>
に変わるから、知りたい関数の名前をそこに打ってEnterを押そう!
help?> sum
search: sum sum! sumabs summary sumabs2 sumabs! sum_kbn sumabs2! cumsum cumsum! consume cumsum_kbn isnumber isalnum SlotNumber
sum(itr)
Returns the sum of all elements in a collection.
sum(A, dims)
Sum elements of an array over the given dimensions.
sum(f, itr)
Sum the results of calling function f on each element of itr.
キーワード検索をする場合は、help?>
プロンプトでキーワードを"..."
で囲んで入力しよう!
help?> "variance"
Base.var
Base.cov
Base.varm
RStudioみたいなのはあるの?
RStudioほどじゃないかもしれないけど、JunoというAtomエディターをベースにした開発環境が人気だよ。他にはEclipse向けのプラグイン(https://github.com/JuliaComputing/JuliaDT)やVS Code向けのプラグイン(https://github.com/JuliaEditorSupport/julia-vscode)もあるよ。
EmacsやVimの人は?
Emacs用プラグイン(https://github.com/JuliaEditorSupport/julia-emacs)とVim用プラグイン(https://github.com/JuliaEditorSupport/julia-vim)ももちろんあるよ。
ノートブックはあるの?
IJulia.jlを使って、Jupyter Notebookを利用できるよ。すぐ試してみるのにJuliaBoxを使うといいんじゃないかな?
CRANみたいなパッケージのレポジトリはあるの?
あるよ!パッケージの一覧がhttp://pkg.julialang.org/から閲覧できるよ。
パッケージのインストールはどうするの?
Rのinstall.packages
に当たるのがPkg.add
だよ。もちろん依存解決もしてくれるよ。
GitHubにしかないパッケージのインストールは?
Pkg.clone
にレポジトリのURLを入れると野良パッケージでもインストールできるよ。ここでも依存解決をしてくれるよ。
Rを呼び出したいんだけど?
RCall.jlを使おう! RCall.jlはJuliaのコードにRを埋め込んだりRのREPLを使ったりできるよ。
Pythonも呼び出したいんだけど?
PyCall.jlを使おう! ついでに、C++が呼び出せるCxx.jlとかMATLABが呼び出せるMATLAB.jlなんてのもあるよ。
CやFortranのコードの呼び出しは?
JuliaはCやFortranのコードを呼び出す仕組みを標準で用意しているよ。 詳しくは後で説明するよ。
Rのサンプルデータを読みたいんだけど?
RDatasets.jlにRでよく使われるデータセットがパッケージされているよ!
ワークスペースを保存するにはどうするの?
JLD.jlの@save
マクロを使おう。
julia> using JLD
julia> n = 5
5
julia> x = randn(n, n)
5×5 Array{Float64,2}:
-2.18618 0.0831716 2.19386 0.806268 1.11444
-0.99689 -0.187922 -0.138358 -0.141601 0.19058
0.0971969 0.149858 -1.19826 1.15507 1.0969
-0.140956 -0.727159 0.780267 1.17143 0.979918
-0.161376 -0.162103 -0.175158 -0.402853 0.916248
julia> @save "workspace.jld"
ワークスペースを復元するには?
同じくJLD.jlの@load
マクロを使おう。
julia> using JLD
julia> @load "workspace.jld"
3-element Array{Symbol,1}:
:ans
:n
:x
julia> n
5
julia> x
5×5 Array{Float64,2}:
-2.18618 0.0831716 2.19386 0.806268 1.11444
-0.99689 -0.187922 -0.138358 -0.141601 0.19058
0.0971969 0.149858 -1.19826 1.15507 1.0969
-0.140956 -0.727159 0.780267 1.17143 0.979918
-0.161376 -0.162103 -0.175158 -0.402853 0.916248
コミュニティ
Juliaのコミュニティはどんな感じなの?
Juliaのコミュニティは小さいけれど、とてもオープンなコミュニティを形成しているよ。 特に初心者に優しい(Juliaユーザはだいたい初心者)ので、気軽にコミュニケーションを取れるよ。
質問はどこですればいいの?
英語ならDiscourseかStack Overflowだね。Stack Overflowで質問する時は"julia-lang"のタグをつけるようにしよう。 Discourseの方がJuliaハッカーが見てる率が高い気がするから、Juliaに限定した質問ならDiscourseの方がオススメかも。
日本語話者のコミュニティは?
日本ではJulia Tokyoが知る限り唯一のコミュニティかな? Facebook・Slack・GitHubのアカウントがあるし、不定期でミートアップも開催してるよ。
他にはどんなコミュニティがあるの?
Juliaのコミュニティは各ドメインに分かれてゆるくつながっているよ。 コミュニティのリストはここ(http://julialang.org/community/)にあるよ。 いくつか挙げると以下のドメインのコミュニティは結構活発かな。
ドメイン | GitHub |
---|---|
統計 | JuliaStats |
最適化 | JuliaOpt |
データベース | JuliaDB |
GPU | JuliaGPU |
生物学 | BioJulia |
微分 | JuliaDiff |
グラフィクス | JuliaGraphics |
計量経済学 | QuantEcon |
これらで活発に開発されているパッケージは概ね安心して使えるかな。
どんな人達がJuliaを使ってるの?
Juliaの使い方は色々あるけれど、やっぱり数値計算目的に使っているユーザが多いかな。 Julia Computing, Inc.の事例を見ると、金融・経済分野を始めとしたデータ分析界隈で使われ始めている印象があるな。
HPCでの事例や科学論文でJuliaを使った計算の実装も最近よく見るようになってきたよ。
他には、StanfordやMITでは技術計算の授業にもJuliaが使われているよ。
トークとかの映像は?
YouTubeのチャンネルにカンファレンスの動画などがたくさんあるよ!
https://www.youtube.com/user/JuliaLanguage
Julia界のHadley Wickhamは?
Julia界には(良くも悪くも)あれくらい目立っている人はいないかな。 Juliaの重要なパッケージは各organizationがコミュニティとして開発・管理してるよ。
文法
Rの構文との対応関係を教えて?
いいよ!
代入
# R x <- 100
# Julia x = 100
分岐
# R if (x == 0) { print("zero") } else if (x < 0) { print("negative") } else { print("positive") }
# Julia if x == 0 println("zero") elseif x < 0 println("negative") else println("positive") end
for文
# R for (i in 1:10) { print(i) if (i > 5) { break } }
# Julia for i in 1:10 println(i) if i > 5 break end end
関数
# R add <- function (x, y) x + y add <- function (x, y) { return (x + y) }
# Julia add(x, y) = x + y function add(x, y) return x + y end
ライブラリ読み込み
# R library(ggplot2)
# Julia using DataFrames
たまにJuliaのコードにある@
は何?
@
から始まるコードはJuliaのマクロ呼出しだよ。
マクロはJuliaのコードを別のコードに書き換えたりするメタプログラミングの一種だよ。
例えば次のコードに出てくる@inbounds
は配列アクセスの境界チェックを無くして少し高速化することができるし、@show
はその時の値をいい感じに表示してくれるよ。
x = randn(10) s = 0 @inbounds for i in 1:endof(x) s += x[i] end @show s
たまにJuliaのコードにあるr"..."
は何?
それもJuliaのマクロだよ!
標準ライブラリではr"..."
と書くことで文字列でなく正規表現を作ることができるよ。
julia> r"foo"
r"foo"
julia> typeof(r"foo")
Regex
julia> b"foo"
3-element Array{UInt8,1}:
0x66
0x6f
0x6f
julia> typeof(b"foo")
Array{UInt8,1}
この仕組はユーザからも拡張可能なので、例えばBio.jlではDNA配列を作ったりするのに使ってるよ。
julia> using Bio.Seq
julia> dna"ACGTAG"
6nt DNA Sequence:
ACGTAG
データ
主な数値型は?
整数はInt
型、倍精度浮動小数点数はFloat64
型がデフォルトでよく使われるよ。
Int
は32-bit環境なら32-bit、64-bit環境なら64-bitで表現されるよ。
Juliaは数値の型も豊富で、8-bitから128-bitまで符号の有り無しの組み合わせがすべて用意されているし、複素数(Complex{T}
)や有理数(Rational{T}
)もあるよ。
TRUE
やFALSE
は?
Juliaでは全部小文字のtrue
とfalse
だよ。
欠損値は扱えるの?
JuliaではNullable{T}
という型があって、型T
の欠損値を表すよ。
nothing
という値もあるけどパフォーマンス上の理由であまりお勧めしないよ。
JuliaもRみたいに常に配列として数値を扱うの?
Juliaでは単一の値と配列は区別されるよ。例えば、3
と書いたら整数の3を意味するし、[3]
と書いたら1つの整数3からなるベクトルだよ。
3
と書いたら浮動小数点数じゃなくて整数なの?
そうだよ、Rと違ってJuliaでは整数として扱われるよ。
じゃぁ浮動小数点数の3を作るには?
3.0
と書けば浮動小数点数として扱われるよ。
どうやって確認するの?
Rみたいにtypeof
という関数を使おう!
julia> typeof(3)
Int64
julia> typeof(3.0)
Float64
R> typeof(3)
[1] "double"
R> typeof(3.0)
[1] "double"
演算子はどんな感じ?
ほとんどRと同じだよ。
julia> 3 + 4
7
julia> 3 - 4
-1
julia> 3 * 4
12
julia> 3 / 4
0.75
R> 3 + 4
[1] 7
R> 3 - 4
[1] -1
R> 3 * 4
[1] 12
R> 3 / 4
[1] 0.75
Bool演算(&&
, ||
, !
)も同じだよ。排他的論理和はv0.5では$
だけど次期バージョンでxor
という名前に変更される予定だよ。
list
はあるの?
Juliaで一番近いのはDict
かな?
Pythonのdict
と同じハッシュを使った連想配列だよ。
julia> d = Dict("one" => 1, "two" => 2, "three" => 3)
Dict{String,Int64} with 3 entries:
"two" => 2
"one" => 1
"three" => 3
julia> d["two"]
2
data.frame
は?
残念ながらJuliaの標準ライブラリにはないよ。でもDataFrames.jlというパッケージがJuliaのデータフレームの標準的な実装だよ。 この辺は後で詳しく述べるよ。
多次元行列も扱えるの?
もちろん扱えるよ! Rのmatrix
みたいなことが次のようにできるよ。
julia> [1 2 3
4 5 6]
2×3 Array{Int64,2}:
1 2 3
4 5 6
julia> [1 2 3; 4 5 6] # セミコロンは改行と同じ扱い
2×3 Array{Int64,2}:
1 2 3
4 5 6
R> matrix(c(1, 2, 3, 4, 5, 6), nrow=2)
[,1] [,2] [,3]
[1,] 1 3 5
[2,] 2 4 6
空の配列の作成は?
ベクトル(1次元配列)なら[]
だよ。
julia> []
0-element Array{Any,1}
ただ要素の型が分かっている場合はそれを教えてあげるとJuliaは高速に動くよ。
julia> Int[]
0-element Array{Int64,1}
julia> Float64[]
0-element Array{Float64,1}
matrix(0, 3, 4)
みたいに0で初期化された配列の作成は?
zeros
関数を使おう!
julia> zeros(3, 4) # 3行4列の行列
3×4 Array{Float64,2}:
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
julia> zeros(Int, 3, 4)
3×4 Array{Int64,2}:
0 0 0 0
0 0 0 0
0 0 0 0
1で初期化された配列ならones
だよ。
配列要素へのアクセスはどうするの?
基本的にRと同じだよ。Juliaの配列も1始まりでcolumn majorだよ。
ただ、Rだとx[i,]
とかx[,j]
と書くところをJuliaではx[i,:]
とかx[:,j]
とか書くよ。
julia> x = [1 2 3; 4 5 6]
2×3 Array{Int64,2}:
1 2 3
4 5 6
julia> x[1,2]
2
julia> x[2,2:3]
2-element Array{Int64,1}:
5
6
julia> x[:,2]
2-element Array{Int64,1}:
2
5
2:3
みたいなのはベクトル?
Juliaではm:n
はm
からn
までの範囲(Range
)を表すよ。
julia> 2:3
2:3
julia> typeof(2:3)
UnitRange{Int64}
ベクトルと違ってメモリーを消費しないので、長い範囲でも一瞬で作れるよ。
character
みたいな文字列型は?
JuliaではString
型があるよ。
文字列はUnicodeも使える?
使えるよ! JuliaではUTF-8でエンコーディングしてあるよ。
文字列操作関数を教えて?
stringrパッケージの関数と大雑把に対応を付けたよ。
R (stringr) | Julia |
---|---|
str_c |
string |
str_detect |
ismatch |
str_extract |
match |
str_dup |
^ |
str_split |
split |
str_replace |
replace |
str_trim |
strip , lstrip , rstrip |
str_pad |
lpad , rpad |
str_to_lower |
lowercase |
str_to_upper |
uppercase |
as.double
とかas.integer
みたいな型変換はどうするの?
Juliaの型変換は基本的にconvert
関数を使うよ。
julia> convert(Float64, 42)
42.0
julia> convert(Int64, 42.0)
42
ただし文字列をパースして数値にするにはparse
を使うよ。
julia> parse(Int, "42")
42
julia> parse(Float64, "42")
42.0
行名や列名はどうつけるの?
Juliaの標準では行名や列名をサポートしていないよ。 でもNamedArrays.jlという配列に名前を付けられるようにするパッケージはあるよ。
欠損値のある配列はどう扱えばいいの?
欠損値の扱いはまだJulia界隈でも完全な合意に達してない課題なので、ちょっとむずかしいな。
前にも述べたようにNullable{T}
というデータ型があるけど、これに関する操作が将来変わる可能性があるよ。
でも、基本的にはNullableArrays.jlを使おう!
因子(factor)を扱うにはどうするの?
これもまだJuliaではちょっと扱いが難しいけど、CategoricalArrays.jlというパッケージがあるよ。 NullableArrays.jlやCategoricalArrays.jlは近いうちにDataFrames.jlに採用される予定のようだよ。
関数
関数はどうやって定義するの?
function
キーワードを使ったスタイルと短縮記法があるよ。
# 通常の記法 function add(x, y) return x + y end # 短縮記法 add(x, y) = x + y
デフォルト引数やキーワード引数は?
あるよ! でもJuliaではデフォルト引数とキーワード引数で記法が分かれているよ。
# デフォルト引数 function default(x, y=1, z=10) return x + y + z end # キーワード引数 (セミコロンに注目) function keyword(x; y=1, z=10) return x + y + z end
試しに実行してみよう
julia> default(1)
12
julia> default(1, 0)
11
julia> default(1, 0, 5)
6
julia> keyword(1)
12
julia> keyword(1, z=5)
7
julia> keyword(1, z=5, y=0)
6
クロージャも作れる?
もちろん!
function counter() i = 0 function x(n=1) i += n return i end end
julia> c = counter()
(::x) (generic function with 2 methods)
julia> c()
1
julia> c()
2
julia> c()
3
julia> c(3)
6
julia> c()
7
+
とかは関数じゃないの?
関数だよ! 前置記法で書くこともできるよ!
julia> +(1, 2)
3
R> `+`(1, 2)
[1] 3
じゃぁRみたいにif
とかfor
も関数なの?
Juliaではif
・for
・while
は関数じゃないよ。
遅延評価はあるの?
Juliaには遅延評価はなくてすべて正格評価だよ。つまり関数に渡された引数は呼び出し前にかならず評価されるよ。
apply
みたいな関数を配列の要素に適用する関数は?
map
を使おう! ただし関数が最初の引数だよ。
julia> map(log, [1,2,3])
3-element Array{Float64,1}:
0.0
0.693147
1.09861
julia> map(exp, [1,2,3])
3-element Array{Float64,1}:
2.71828
7.38906
20.0855
R> sapply(c(1,2,3), log)
[1] 0.0000000 0.6931472 1.0986123
R> sapply(c(1,2,3), exp)
[1] 2.718282 7.389056 20.085537
ただ最近はブロードキャストを使う方法が一般的なので、こちらを使ったほうが良いよ。
julia> log.([1,2,3])
3-element Array{Float64,1}:
0.0
0.693147
1.09861
julia> exp.([1,2,3])
3-element Array{Float64,1}:
2.71828
7.38906
20.0855
この関数名の後のドットは何?
ブロードキャスト関数呼び出しの構文糖衣だよ。例えばf.(x)
はbroadcast(x, f)
に変換されるよ。
broadcast
はmap
の凄い版だよ。
ベクトル同士の足し引きとかは?
+
や-
がそのまま使えるよ。でも.
を付けた.+
や.-
なら左右で配列のサイズが違った場合でもRのように動いてくれるよ。
julia> [1,2,3] + [4,5,6]
3-element Array{Int64,1}:
5
7
9
julia> [1,2,3] .+ [4]
3-element Array{Int64,1}:
5
6
7
julia> [1,2,3] .+ 4
3-element Array{Int64,1}:
5
6
7
R> c(1, 2, 3) + c(4, 5, 6)
[1] 5 7 9
R> c(1, 2, 3) + c(4)
[1] 5 6 7
R> c(1, 2, 3) + 4
[1] 5 6 7
書いた関数が思ったより遅いんだけど?
一番よくある原因が関数内で使われている変数の型が不安定になっているせいだよ。
例えば次のコードはxs
の要素がInt
のときには高速に動くけど、浮動小数点数の配列とかを渡すとすごく遅いよ。
function sumup(xs) s = 0 for x in xs s += x end return s end
julia> xs = rand(1:10, 10000);
julia> sumup(xs); # ウォームアップ
julia> @time sumup(xs)
0.000011 seconds (5 allocations: 176 bytes)
54649
julia> xs = rand(10000);
julia> sumup(xs); # ウォームアップ
julia> @time sumup(xs)
0.000337 seconds (30.00 k allocations: 468.906 KB)
5015.030044010504
原因は変数s
は整数型で初期化されてるのに、ループの中で浮動小数点数と足すことでs
の型が整数か浮動小数点数かJuliaには分からなくなってしまうせいだよ。これをJulia界隈では型の不安定さ(type instability)と言ってるよ。
これを回避するにはeltype
とzero
関数を次のように使うことで、s
の型をxs
に合わせて設定できるよ。
function sumup(xs) s = zero(eltype(xs)) for x in xs s += x end return s end
こうすることでさっきより何十倍も速くなったね!
julia> sumup(xs);
julia> @time sumup(xs)
0.000015 seconds (5 allocations: 176 bytes)
5015.030044010504
関数のプロファイルを取るにはどうするの?
@profile
マクロと、Profile.print()
をあわせて使おう!
@profile <code>
でコード実行のプロファイルをとって、Profile.print()
でプロファイル結果をプリントできるよ。
C言語の関数の呼び出し方法を教えて!
Juliaにはccall
というC言語を呼び出す専用の命令があるよ。例えば、GNU libcに実装されているexp
関数を呼び出すなら次のように書けるよ。
julia> ccall((:exp, "libc"), Float64, (Float64,), 1.0)
2.718281828459045
julia> ccall((:exp, "libc"), Float64, (Float64,), -1.0)
0.36787944117144233
ccall
は最初に呼び出す関数を指定して、その後に返り値の型と引数の型を指定して、最後に引数を渡すよ。上の例で言えば、(:exp, "libc")
が呼び出す関数で、Float64
が返り値の型、(Float64,)
が引数の型で、1.0
や-1.0
がCの関数に渡される実際の引数だよ。
Juliaは数値以外にも文字列やより複雑な構造体をCのライブラリとやり取りすることができるように設計されているので、より詳しくはマニュアルのここを読んでみてね。 具体的な例としてはJuliaの標準ライブラリやLibz.jlなんかが勉強になるよ。
型システムとメソッド
Juliaの型システムはどうなってるの?
Juliaの型は大きく具体型(concrete type)と抽象型(abstract type)に分けられていて、メソッドの選択は多重ディスパッチというシステムを中心に構築されているよ。
具体型とか抽象型って何?
具体型はインスタンス化(オブジェクトを作れる)できる型で、抽象型はそうでない型だよ。
例えばFloat64
は1.0
など値を作れるけどReal
という抽象型は値を作れないよ。
値を作れない抽象型は何の役に立つの?
複数の型をまとめるのに使われるよ。KeitaNakamuraさんのコードを使わせてもらうと、例えばReal
を頂点とする型は次のような構造になっているよ。
julia> print_tree(Real)
Real
├─── AbstractFloat
| ├─── BigFloat
| ├─── Float16
| ├─── Float32
| └─── Float64
├─── Integer
| ├─── BigInt
| ├─── Bool
| ├─── Signed
| | ├─── Int128
| | ├─── Int16
| | ├─── Int32
| | ├─── Int64
| | └─── Int8
| └─── Unsigned
| ├─── UInt128
| ├─── UInt16
| ├─── UInt32
| ├─── UInt64
| └─── UInt8
├─── Irrational{sym}
└─── Rational{T<:Integer}
function print_tree(typ) println(typ) print_tree(typ, []) end function print_tree(typ, level) stypes = subtypes(typ) for stype in stypes if stype !== stypes[end] println(join(level) * "├─── $stype") push!(level, "| ") else println(join(level) * "└─── $stype") push!(level, " ") end print_tree(stype, level) pop!(level) end end
クラス(型)の定義はどうやるの?
RのS4に似た仕組みがJuliaにはあるよ。例えば以下の2つの定義は大体対応しているよ。
# R setClass("Person", representation(name = "character", age = "numeric"))
# Julia type Person name::String age::Int end
Juliaはデフォルトコンストラクタを準備しているから、オブジェクトは次のように作成できるよ。
julia> hadley = Person("Hadley", 31)
Person("Hadley",31)
julia> hadley.name
"Hadley"
julia> hadley.age
31
ただし、Juliaのtype
で宣言したオブジェクトは変更可能なのでその振る舞いはRの参照クラス(RC)に近いかな。
継承は?
JuliaはsetClass
のcontains
引数に当たるような継承の仕組みはないよ。つまり、複数の型間で構造を継承する方法はないよ。
その代わり、定義する型をある抽象型のサブタイプにする仕組みはあって、Juliaではこちらを使うよ。
例えば、Juliaで要素の数が3つのベクトルっぽい型を作る場合には、次のようにAbstractVector
のサブタイプとして宣言すると良いよ。
type Triplet{T} <: AbstractVector{T} x::T y::T z::T end
ここで、T
と書かれているのは型パラメータで、T = Int64
やT = Float64
が代入されて具体的な型になるよ。
また、各抽象型には実装していることが期待されるメソッドがあるので、それを実装するべきだよ (AbstractVector
ならBase.size
・Base.getindex
などを実装すべき)。
メソッドの定義は?
通常の関数定義に型指定したものがRのsetMethod
に近いよ。
# R setGeneric("greet", function(x) standardGeneric("greet")) setMethod("greet", signature("Person"), function(x) paste("Hello, ", x@name))
# Julia function greet(x::Person) return string("Hello, ", x.name) end
型指定は複数に引数に対しても指定できるし、抽象型も指定できるよ。これをJuliaでは多重ディスパッチと言うよ。
前処理
CSV/TSVファイルの読み込みは?
3通りの方法があるよ!
- 標準ライブラリの
readcsv
- DataFrames.jlの
readtable
- CSV.jlの
CSV.read
どれがいいの?
標準ライブラリのreadcsv
は標準なのですぐ使えるけど、行列としてデータを読み込むから、場合によってはちょっと使いづらいかな?
DataFrames.jlのreadtable
はデータフレームを読み込むけど、将来的に使われなくなる可能性が高いよ。
なので、現在ではCSV.jlのCSV.read
を使うのがオススメだよ。
julia> using DataFrames
julia> using CSV
julia> head(CSV.read("iris.csv"))
6×5 DataFrames.DataFrame
│ Row │ SepalLength │ SepalWidth │ PetalLength │ PetalWidth │ Species │
├─────┼─────────────┼────────────┼─────────────┼────────────┼──────────┤
│ 1 │ 5.1 │ 3.5 │ 1.4 │ 0.2 │ "setosa" │
│ 2 │ 4.9 │ 3.0 │ 1.4 │ 0.2 │ "setosa" │
│ 3 │ 4.7 │ 3.2 │ 1.3 │ 0.2 │ "setosa" │
│ 4 │ 4.6 │ 3.1 │ 1.5 │ 0.2 │ "setosa" │
│ 5 │ 5.0 │ 3.6 │ 1.4 │ 0.2 │ "setosa" │
│ 6 │ 5.4 │ 3.9 │ 1.7 │ 0.4 │ "setosa" │
詳しい使い方はCSV.jlのヘルプを参照してね。
SQLiteのデータを読みたいんだけど?
SQLite.jlを使おう。SQLを発行した結果はDataFrame
として返してくれるよ。
JSONを扱うには?
JSON.jlを使おう。
julia> using JSON
julia> JSON.parse("""{"foo": 100, "bar": [1.1, 2.0]}""")
Dict{String,Any} with 2 entries:
"bar" => Any[1.1,2.0]
"foo" => 100
XMLやHTMLのデータを処理するには?
拙作のEzXML.jlがオススメだよ。長く使われているLightXML.jlというのもあるけれど、機能が少ないし、あまりインターフェースがきれいじゃないね...
julia> using EzXML
julia> readxml("ex1.xml")
EzXML.Document(EzXML.Node(<DOCUMENT_NODE@0x00007fcda9030400>))
julia> readxml("ex1.xml") |> print
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book category="COOKING" tag="first">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
</bookstore>
でもHTMLに関してはGoogleのgumboをラップしたGumbo.jlの方が良いかな?
dplyrは?
残念ながらdplyrほどの完成度のパッケージは今のところJuliaには無いかな。 ただJuliaの人にはR使いも多いので、dplyrの利便性は誰もが認めるところだね。 それで最近は似たことをJuliaでやろうとしている人達が出てきて、いくつかパッケージができているよ。
Juliaのデータフレームの近況と将来についてはここにまとまってるよ。
UTF-8以外のファイルを読み込みたいんだけど?
StringEncodings.jlを使おう! libiconvを使ってるのでSJISみたいなアレなエンコーディングでもちゃんと扱えるよ!
統計/機械学習
平均とか分散の計算は?
mean
, median
, var
, cov
, cor
はRと同名の関数が標準ライブラリにあるよ。標準偏差はsd
でなくstd
になってるので注意だよ。
rowSums
とかcolSums
とかは?
sum(array, dim)
が使えるよ。
julia> x
2×3 Array{Int64,2}:
1 2 3
4 5 6
julia> sum(x, 1) # colSums
1×3 Array{Int64,2}:
5 7 9
julia> sum(x, 2) # rowSums
2×1 Array{Int64,2}:
6
15
他にもmean
やvar
なんかでも同じことができるよ。
乱数生成はどうするの?
一様乱数runif(n)
に対応するのはrand(n)
、正規分布rnorm(n)
に対応するのはrandn(n)
だよ。
ガンマ分布とかポアソン分布みたいな他の分布は?
Distributions.jlを使おう。
PCA(主成分分析)をしたいんだけど?
MultivariateStats.jlを使おう。
julia> using MultivariateStats
julia> using RDatasets
julia> iris = dataset("datasets", "iris")
julia> pca = fit(PCA, Matrix(iris[:,1:4])') # 行列に変換して転置
PCA(indim = 4, outdim = 3, principalratio = 0.99479)
julia> transform(pca, Matrix(iris[:,1:4])')
3×150 Array{Float64,2}:
2.68413 2.71414 2.88899 2.74534 2.72872 2.28086 … -1.94411 -1.52717 -1.76435 -1.90094 -1.39019
0.319397 -0.177001 -0.144949 -0.318299 0.326755 0.74133 0.187532 -0.375317 0.0788589 0.116628 -0.282661
0.0279148 0.210464 -0.0179003 -0.0315594 -0.0900792 -0.168678 -0.177825 0.121898 -0.130482 -0.723252 -0.36291
GML(一般化線形モデル)を扱いたいんだけど?
GLM.jlを使おう。Rでも有名なDouglas Bates先生の作ったパッケージだから安心だね!
Lassoは?
Julia実装のLasso.jlかglmnetをラップしたGLMNet.jlが良いと思うよ。
Stanを使いたいんだけど?
Stan.jlを使おう!
行列積の計算は?
Rの%*%
がJuliaでは*
だよ。
julia> A = randn(2, 3);
julia> B = randn(3, 4);
julia> A * B
2×4 Array{Float64,2}:
2.42828 -1.39917 0.28215 -1.61981
0.292669 -0.411146 0.234041 1.82686
R> A <- matrix(rnorm(6), 2)
R> B <- matrix(rnorm(12), 3)
R> A %*% B
[,1] [,2] [,3] [,4]
[1,] 0.8448714 -0.6084743 -0.7458281 -0.653775
[2,] 0.2133118 -0.9305351 0.3933490 1.105863
solve
みたいな線形方程式の解の計算は?
A * X = B
を解くには、A \ B
と計算するよ。
julia> A = randn(3, 3)
3×3 Array{Float64,2}:
0.864382 -0.945314 -0.496754
0.119709 0.83993 -0.962021
-0.737123 0.221293 0.205341
julia> X = randn(3, 2)
3×2 Array{Float64,2}:
-0.224859 1.64374
0.0726233 0.898944
-0.387804 0.697085
julia> B = A * X
3×2 Array{Float64,2}:
-0.0703728 0.224752
0.407157 0.28121
0.102188 -0.869566
julia> A \ B
3×2 Array{Float64,2}:
-0.224859 1.64374
0.0726233 0.898944
-0.387804 0.697085
julia> A \ B ≈ X
true
A
が実対称行列であることが分かってるんだけど?
cholfact(A)
でコレスキー分解してからでも同様に計算できるよ!
julia> A = randn(3, 3);
julia> A = A'A
3×3 Array{Float64,2}:
1.13074 2.65079 -0.0602727
2.65079 7.18991 -0.474305
-0.0602727 -0.474305 0.488933
julia> issymmetric(A)
true
julia> X = randn(3, 2)
3×2 Array{Float64,2}:
0.809623 0.595824
-0.25161 -2.33654
0.97426 1.46868
julia> B = A * X;
julia> A \ B
3×2 Array{Float64,2}:
0.809623 0.595824
-0.25161 -2.33654
0.97426 1.46868
julia> cholfact(A) \ B
3×2 Array{Float64,2}:
0.809623 0.595824
-0.25161 -2.33654
0.97426 1.46868
log(sum(exp(x)))
とかlog(exp(x)+1)
とかを安定的に計算したいんだけど?
StatsFuns.jlに標準ライブラリにはないけど、数値計算でよく見る関数の実装があるからそっちを探してみよう。
julia> x = randn(10)
10-element Array{Float64,1}:
0.0410153
-0.407537
1.26758
-0.52522
-0.0969684
-2.52716
-0.633663
-1.2695
-0.474312
-0.800294
julia> log(sum(exp(x)))
2.165782421890441
julia> logsumexp(x)
2.165782421890441
julia> log(sum(exp(1000x))) # 1000倍すると計算できない
Inf
julia> logsumexp(1000x) # StatsFuns.jlの実装なら大丈夫
1267.5802777924926
プロット作るにはどうするの?
残念ながら、今のところJuliaには決定的なプロットのための仕組みがあるわけではないので、次のものを参考に好みに合ったのを使おう!
- Gadfly.jl
- 最も歴史のあるJuliaの
- 一番スターが多い
- Plots.jl
- 新興で急成長中のライブラリ
- 開発が活発
- PlotlyJS.jl
- Plot.lyのラッパーライブラリ
- インタラクティブな可視化が可能
- PyPlot.jl
- Pythonのmatplotlibのラッパーライブラリ
- 最も高機能かも
- GR.jl
- GRフレームワークのラッパーライブラリ
- パフォーマンスに優れる
- UnicodePlots.jl
- Unicode文字を使ってターミナル上でプロットできるライブラリ
- ごく簡単なプロットをチラッと見たいときに向いてる
関数の最大・最小値を探したいんだけど?
Optim.jlを使おう!
julia> using Optim
julia> rosenbrock(x) = (1.0 - x[1])^2 + 100.0 * (x[2] - x[1]^2)^2
rosenbrock (generic function with 1 method)
julia> optimize(rosenbrock, randn(2))
Results of Optimization Algorithm
* Algorithm: Nelder-Mead
* Starting Point: [0.4289709134439212,-1.3041568238701216]
* Minimizer: [1.0000314591723356,1.0000648302370259]
* Minimum: 1.354834e-09
* Iterations: 69
* Convergence: true
* √(Σ(yᵢ-ȳ)²)/n < NaN: false
* Reached Maximum Number of Iterations: false
* Objective Function Calls: 136
R> rosenbrock <- function (x) (1 - x[1])^2 + 100 * (x[2] - x[1]^2)^2
R> optim(rnorm(2), rosenbrock)
$par
[1] 0.9995882 0.9991591
$value
[1] 2.002849e-07
$counts
function gradient
129 NA
$convergence
[1] 0
$message
NULL
Juliaの機械学習パッケージはどうなってるの?
Juliaユーザには機械学習を常用する人も多いけれど、PythonやRほど充実はしてないよ。 一応、ScikitLearn.jlというscikit-learnのラッパーがJuliaのパッケージになってるので、scikit-learnでできることはできるはずだよ。
Gradient boostingとかは?
XGBoost.jlが使えるよ! 名前から分かる通りXGBoostへのJuliaのインターフェースだよ。
Deep learningのライブラリは?
MXNet.jlというMXNetのパッケージがあるよ。またDMLCだね。他にはMocha.jlというJulia製のフレームワークもあるけれど、メインの開発者がmxnetの方に移ったようなので今はそれほど開発が活発でもないかな。 他にはTensorFlow.jlというPythonのTensorFlowのラッパーライブラリもあるよ。
もっとJuliaっぽいやつだと、Transformations.jlとかNAISTの進藤先生が作っているMerlin.jlというパッケージもあるよ。
結局、Juliaでデータ解析はどうなの?
Juliaは機械学習のアルゴリズムを組むには良い言語だと思うけど、ライブラリやツールもとても少ないね。 なので現状、インタラクティブな探索的データ解析(exploratory data analysis)をするような場合にはJuliaよりRの方をオススメするよ。
ただ、最近JuliaMLという機械学習に特化したコミュニティができてJuliaで気軽に機械学習ができるようにしていたり、データフレームに本格的に見直そうとしていたりとか、改善に向けて動いているよ。今ならやることがたくさんあるので、活動に参加してみてはどうかな?
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.")です。ここで言う「他の技術計算環境」とは、MATLABやPythonのことを指していると考えられます。実際、Juliaの構文や関数名はMATLABとの類似性が高いため、Octaveのようなオープンソース版のMATLAB代替と捉えられることも多いようですが、プログラミング言語としてはかなり性質が異なります。また、Pythonとは文法や関数名の面ではそれほど類似性は無いように思いますが、似ている点も多数あります。
Juliaの最大の特徴を挙げるならば、動的プログラミング言語でありながらC言語などに匹敵するほど実行が高速であるという点でしょう。多くの技術計算では関数の呼び出しやループが頻繁に用いられますが、Juliaでは実行時コンパイル(JIT)のおかげでこれらの実行コストがネイティブコードにコンパイルするプログラミング言語並になっています。しかし、Juliaが行う実行時のコンパイルは、他の言語でよくみられるJITコンパイラとは性質を異にするようです。PyPy(PythonのJITコンパイラ実装)やLuaJIT(LuaのJITコンパイラ実装)は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さんの担当です。
Julia v0.4 新機能&変更点まとめ
待ちに待ったJuliaの新バージョンv0.4がリリース直前です!
現在はRC1RC2が利用可能ですので、新しいもの好きの方はダウンロードして試すことができます。
Releases · JuliaLang/julia · GitHub
v0.4はかなり長い間開発されてきましたので、新機能が盛りだくさんになっています。 すべての変更点を紹介することはできませんので、完全なリストは本家のリリースノートを参照してください。 注目度の高いものから順に見て行きましょう。
ドキュメント機能とパッケージのコンパイル機能追加!
パッケージを作る人にもユーザにも嬉しい機能追加がこの2つです。 ドキュメント機能は今まで標準ライブラリにしかなかったドキュメントが、パッケージ作成者や他のユーザも追加できるようになりました。 また、パッケージのコンパイル機能では、サードパーティーのパッケージが初回の実行時に自動的にコンパイルされるようになり、パッケージの読み込み時間がかなり短縮されるようになりました。
ドキュメント機能
ドキュメント機能では、ドキュメントをメソッドや型の定義の部分に書くことができます。
例えば、以下のようにPoint
型と、distance
というユークリッド距離を求める関数を書いたとしましょう。
""" Point type. This is a type to represent a point in 3D Cartesian coordinate system. Fields: * `x`: coordinate of X axis. * `y`: coordinate of Y axis. * `z`: coordinate of Z axis. """ type Point x::Float64 y::Float64 z::Float64 end "Enclidean distance from the origin." function distance(p::Point) sqrt(p.x^2 + p.y^2 + p.z^2) end
ドキュメントはMarkdown形式で記述でき、型やメソッドの直前に文字列リテラルとして記述します。
こうしたドキュメントは、JuliaのREPLからもヘルプ機能(?
コマンド)で参照できます。
パッケージのコンパイル機能
パッケージのコンパイル機能では、パッケージの作者がモジュールがコンパイル可能であることを明示的に示すことによって、初回の利用時に自動的にコンパイルされます。 また、パッケージに変更があった場合には、それを検出して次の利用時に自動的にコンパイルしますので、古いコードが利用されるなどの心配はありません。
パッケージの作者は、__precompile__()
関数をモジュール定義の直前に呼び出すだけでこの新機能に対応できます。
ただし、互換性の問題からVERSION >= v"0.4.0-dev+6521" && __precompile__()
と書くことが推奨されています。
私は、バージョン番号を覚えるのが面倒なので、isdefined(Base, :__precompile__) && __precompile__()
と書いています。
実例はこちらsrc/DocOpt.jlを参照してください。
実際に試してみると、DocOpt.jlのような小さなライブラリでも、読み込み時間が半分以下に短縮されています。
~/v/julia (master|…) $ julia3 -e 'tic(); using DocOpt; toc()' elapsed time: 1.093200985 seconds ~/v/julia (master|…) $ julia4 -e 'tic(); using DocOpt; toc()' elapsed time: 0.430147529 seconds
- Module initialization and precompilation: http://julia.readthedocs.org/en/release-0.4/manual/modules/#module-initialization-and-precompilation
言語機能の強化
v0.4には言語機能の強化がいくつか入りましたので、いくつか重要なものをかいつまんで紹介します。
関数呼び出し構文のオーバーロード
call
というメソッドを定義することで、関数呼び出しの構文をオーバーロードすることができます。
これは、x(...)
という構文がcall(x, ...)
というように変換されますので、対応するcall
メソッドを定義するだけで利用できます。
この機能は特にコンストラクタを定義する際に便利で、例えば配列の生成がVector(Int, 10)
ではなくVector{Int}(10)
のように呼べるようになりました。
これは、call{T}(::Type{Vector{T}}, m::Integer) = ...
というメソッドが定義されているためです。
関数の生成機能 (@generated
)
少しわかりにくい機能なのですが、極めて強力な新機能のひとつに生成関数 (generated function) というものがあります。訳語は適当です。 これは、メタプログラミングのひとつで、引数の型の組み合わせから式を生成するというものです。 以下の様な対応表を考えてみると、少し分かりやすいかもしれません。
機能 | ドメイン → コドメイン |
---|---|
関数 | 値 → 値 |
マクロ | 式 → 式 |
生成関数 | 型 → 式 |
生成関数の式への展開は初回の呼び出し時のみ起こり、それ以降は普通の関数のように使えます。
より詳しい仕組みはJuliaCon 2015のJake Bolewski氏のトークを参照してください。
Jake Bolewski: Staged programming in Julia - YouTube
- Generated functions: http://julia.readthedocs.org/en/release-0.4/manual/metaprogramming/#generated-functions
コンストラクタとしてのconvert
メソッド
コンストラクタの呼び出しが、convert
へとフォールバックするようになりました。
ですので、以下の例を見ると分かるのですが、ある型にconvert
を定義することでコンストラクタのように振る舞うことができます。
convert
の方がコンストラクタより多機能ですので、引数がひとつであり、かつある値の型変換という意味を伴う場合にはconvert
を定義するほうが良いかもしれません。
type Foo x Foo(x::Int) = new(x) end Base.convert(::Type{Foo}, x::Integer) = Foo(convert(Int, x)) @show Foo(1) # ok both on v0.3 and v0.4 @show Foo(0x01) # ok on v0.4, but not on v0.3
- Constructors, Call, and Conversion: http://julia.readthedocs.org/en/release-0.4/manual/constructors/#constructors-call-and-conversion
性能・機能向上など
標準ライブラリや処理系の最適化も進められています。 いくつか重要そうなものを挙げておきましょう。
新しい世代別GCの導入
一新されたGCにより、一回のGCの時間が短縮され遅延が緩和されるようになりました。
SubArray
SubArray
の設計が見直され、様々な操作がより効率的になりました。
これは、先ほど紹介した新機能の生成関数によるところでもあります。
より正確なコード行情報
今までかなりいい加減だったスタックトレースなどのコード行の情報がより正確になりました。 デバッグやパフォーマンスチューニングなどで非常に重要な情報ですのでありがたいです。
その他新機能
言語機能には直接関与しませんが、様々な新機能がv0.4で導入されました。 Juliaをより多目的に使えるようになる機能です。
Nullable{T}
型
値が存在しないことを示す型Nullable{T}
が導入されました。
これはHaskellのMaybe
などに対応するもので、計算の失敗を示す返り値として用いられる事が多いです。
例えば、v0.4で導入されたtryparse
は文字列をうまくパースで来たときには中身のあるNullable
を、失敗した時には空のNullable
を返します。
julia> tryparse(Int, "123") Nullable(123) julia> tryparse(Int, "notint") Nullable{Int64}()
日付や時刻の型
DateTime
やDate
など、日付や時刻を表す型が追加され、様々な演算が定義されています。
例えば、現在時刻はnow
関数で取得できます。
julia> now() 2015-09-13T13:41:42 julia> typeof(now()) DateTime
明示的な関数のインライン化
関数のインライン化は以前からありましたが、新しく導入された@inline
マクロを使うことでコンパイラに関数のインライン化を強制できます。
くり返し呼ばれる関数では@inline
を使うことでパフォーマンスが向上することがあります。
使い方は簡単で、関数定義の直前に@inline
とつけるだけです。
@inline function getindex(x::T, i::Integer) ... end
その他変更
構文や名前の変更で重要なものをまとめておきます。
辞書の構文
v0.3では["one" => 1, "two" => 2]
のように辞書を書いていたのですが、この構文が非推奨になりました。
v0.4ではDict("one" => 1, "two" => 2)
のようにコンストラクタ呼び出しを使います。
Union
、Tuple
の構文
v0.3ではUnion(T1, T2)
、Tuple(T1, T2)
のように書いていたのですが、一貫性のためそれぞれUnion{T1, T2}
、Tuple{T1, T2}
という構文に変更されました。
型に関しては一貫して{ }
を使うようです。
型名の変更
Uint
やUint32
などの名前がそれぞれUInt
, UInt32
という名前に変更になりました。
前の名称を使っても特に警告はありませんが、将来的にはすべて後者に置き換えられると思いますので、UInt
やUInt32
などと書きましょう。
また、String
がAbstractString
に変更されました。
int
やfloat
などの関数が非推奨
v0.3では、文字列や型の変換にint
やfloat
関数を使っていたのですが、これが非推奨となりました。
文字列をパースしてInt
型に変換する際にはparse(Int, str)
を、別の数値型からInt
にする際はInt(x)
を利用してください。
Juliaのシンボルとは?
先ほど何となくstackoverflowのJuliaに関する投稿を見ていたら、なるほど確かに最初は分からないかもしれないなと思う疑問と、それに対する分かりやすいKarpinski氏の回答があったので紹介しようと思います。
シンボル(Symbol
)とは何か
Juliaを使っていると、シンボルというよく分からない型に出会うことがあります。
PythonやRを使っている人にとっては、初めて目にするものかもしれません。
Rubyを使っている人は名前は知っていると思いますが、Juliaではまた違った使い方がされます。
例えば、REPLで:foo
と打って、型を確認するとそいつが出現します。
julia> :foo :foo julia> typeof(:foo) Symbol
この Symbol
とは一体何であって、なんのために使うのでしょうか?
ひとことで言えば、『シンボルはメタプログラミングにおいて変数を表すのに使われる (a symbol is used to represent a variable in metaprogramming)』ものです。
これだけでは分からないので、少し例を出しつつ説明していきましょう。
Juliaは、偉大なプログラミング言語でアイデアの源泉であるLispから多くのものを受け継ぎました。
それは、プログラム自体をプログラムで操作する能力です。
JuliaはプログラムのコードをJuliaのデータ構造として持つことができ、コードを書き換えることができます。
:(...)
もしくはquote ... end
のようにコードをラップ(quote: クォート)することで、そのコード自体を表現するデータ構造を作り出すことができます。
コード x + 1
をクォートすると :(x + 1)
になるといった具合です。
このデータ構造は Expr
と命名されています。
julia> :(x + 1) :(x + 1) julia> typeof(:(x + 1)) Expr
このデータ構造を詳しく見てみると、以下の様な構造を指定ます。
julia> xdump(:(x + 1)) Expr head: Symbol call args: Array(Any,(3,)) 1: Symbol + 2: Symbol x 3: Int64 1 typ: Any::DataType <: Any
ここに、3つのシンボル(Symbol
)が出てきていることに気がつくと思います。
call
については、Juliaが付加したものですが、+
とx
は両方とも元々のコード x + 1
に含まれていたシンボルです。
Expr: - `call` (Symbol) - `+` (Symbol) - `x` (Symbol) - `1` (Int64)
個々のシンボルは、以下のように取り出すこともできます。
julia> ex = :(x + 1) :(x + 1) julia> ex.args[1] :+ julia> ex.args[2] :x julia> ex.args[3] 1
+
や x
といったシンボルは、元々のコード x + 1
の中で変数として使われていたものです (+
のような演算子もJuliaでは他の変数と変わりありません)。
すなわち、シンボルはJuliaのコードの変数を表すのに使われるデータということになります。
そう考えると、Juliaのコードは数値 (42
) や文字列 ("foo"
) などのリテラルやコメントを除くとほとんどシンボルだということがわかると思います。
const
や=
でさえも、Julia内部ではシンボルとして扱われています。
julia> xdump(:(const W = WORD_SIZE)) Expr head: Symbol const args: Array(Any,(1,)) 1: Expr head: Symbol = args: Array(Any,(2,)) 1: Symbol W 2: Symbol WORD_SIZE typ: Any::DataType <: Any typ: Any::DataType <: Any
シンボルをどう使うか
Juliaのシンボルは二通りの使われ方をします。ひとつはコードの中での効率的な文字列としてですが、より重要なのがメタプログラミングでの利用です。
先ほど述べたように、Juliaのコードは Expr
というデータ構造で表現できるため、Juliaからこのデータ構造を操作することでプログラムを自由に作ることができます。
さらに、これも先ほど見たようにコードの多くはシンボルで構成されるため、シンボルをうまく操ってプログラムを生成する必要があるわけです。
Rubyなどでもシンボルはあるようなのですが、Rubyでは主に String interning としてシンボルを使っているようです。
Expr
とシンボルを使ってコードを生成してみましょう。
先ほどみた x + 1
というコードを生成してみます。
julia> Expr(:call, :+, :x, 1) :(x + 1)
Expr
を使ってコードができることが分かりました。
コードは実行することができるはずです。これには eval
関数を使います。
julia> eval(Expr(:call, :+, :x, 1)) ERROR: UndefVarError: x not defined
変数 x
が無いというエラーになりました。
では x
の値を定義して、もう一度やってみましょう。
julia> x = 10 10 julia> eval(Expr(:call, :+, :x, 1)) 11
今度は問題なく実行できました。
もっとも、 Expr
を使ってコードを生成するより :(...)
や quote ... end
を使ったほうが楽なので、通常はこちらを使います。
しかし、こういうものを使う場合でも、自分がシンボルを含んだ式を作っているということをはっきりと意識することが重要だと思います。
macro
によるマクロの定義と @<macro>
によるマクロ呼出しで、生成したコードをその場に埋め込むことができます。
julia> macro x_plus_one() :(x + 1) end julia> @x_plus_one 11
マクロはREPLで使うこともありますが、実際のコードでは以下のように関数定義の中で呼び出すことが多いでしょう。
julia> function plus_one(x) @x_plus_one end plus_one (generic function with 1 method) julia> plus_one(3) 4
@x_plus_one
マクロは、 x + 1
というコードを生成しますから、以下のように関数の引数を y
にしてしまうと違った動作をします。
julia> function silly_plus_one(y) @x_plus_one end silly_plus_one (generic function with 1 method) julia> silly_plus_one(3) 11
このとき、silly_plus_one(y)
関数は以下の定義と同じ意味ですから、引数は無視されて外側の変数 x
を拾ってしまうことになります。
:(x + 1)
という Expr
が、 :x
というシンボル名を使っていることを強く意識することが重要です。
function silly_plus_one(y) x + 1 end
もうちょっとシンボルを使った簡単なメタプログラミングの実例を見てみましょう。
次のコードは、AからZまでのアルファベットのASCIIコードを保持する定数 ord_A
, ord_B
, ..., ord_Z
を生成するプログラムです。
julia> for char in 'A':'Z' @eval const $(symbol(string("ord_", char))) = $(Int(char)) end julia> ord_A 65 julia> ord_G 71
AからZまですべての定数を手で書いたら大変ですので、メタプログラミングの技法を使うわけです。
ここでは、定数名 ord_<X>
をまず文字列として作り、そこからシンボルを symbol
関数で生成しています (この部分は symbol("ord_", char)
でも問題ありませんが、説明のため一度文字列として変数を生成してからシンボルに変換しています)。
julia> string("ord_", 'A') "ord_A" julia> symbol(string("ord_", 'A')) :ord_A
$(...)
はスプライシング(splicing)や挿入(interpolation)などと言って、コードを評価してその部分に差し込むことができる仕組みです。
例えば、最初の char = 'A'
の段階では、
@eval const $(symbol(string("ord_", 'A'))) = $(Int('A'))
すなわち、
@eval const $(:ord_A) = $(65)
となって、これが quote ... end
と eval
の機能を合わせた @eval
マクロにより
const ord_A = 65
と変換されて、通常の定数の宣言のように振る舞います。 このように、シンボルを使ってコードの変数を生成することで、Julia自身でJuliaのプログラムを自由に生成することができます。
Juliaのメタプログラミングを理解するためにはシンボルをちゃんと理解することが欠かせません。 メタプログラミングをしなくても十分Juliaは強力な言語ですが、これらを使いこなすことでさらに冗長なコードを排除したりパフォーマンスを上げることも可能です。 メタプログラミングについては、Juliaのマニュアル Metaprogramming に詳細な説明がありますので、このあたりに興味のある方は是非確認してみてください。
株式会社リブセンスのTechNightに参加してきました
リブセンスのエンジニアの方にJuliaの話をしてほしいとの話を受けまして、LTですがJuliaのお話をしてきました。 このようなエンジニア中心の勉強会を毎月行っているようです。
LT大会「TechNight」、7/27(月) 19:30〜開催します。 | LIVESENSE made*
せっかくですので、このブログ記事で内容を簡単にまとめておこうと思います。 タイトルは「Juliaは何処から来たのか、Juliaとは何か、Juliaは何処へ行くのか」ということで、Juliaの発端と機能、それに今後どうなっていくのかを簡単にお話しました。
事前にJuliaを知っているかということを尋ねたのですが、20人ほどいるなかで、ひとりだけ去年ちょっと触ってみたという方がいらっしゃいました。 それ以外の方は、Juliaのことを初めて知ったようです。
Juliaは何処から来たのか
Juliaの開発はボストンのMITで始まり、現在でも開発の中心になっています。
Founders
言語を始めにつくった人達は次の4人です。
Jeffはプログラミング言語のスペシャリストで、femtolispという非常に小さいSchemeのような言語の実装を作っています。これは、Julia本体のフロントエンド部分にも使われています。 Edelman先生はMITの教授であり、JeffのPh.D.の指導教員でもあるようです。 より詳しい経緯はWIREDの記事にもなっていますので、読んでみてください。
Repository
開発はすべてGitHubでオープンに行われていて、だれでも参加することができます。
Why They Created Julia
そもそも何故新しいプログラミング言語が必要なのでしょうか。 その理由は、Juliaの最初のブログ記事で説明されています。
In short, because we are greedy.
一言で言えば、貪欲だからだ。
これは、先ほどのブログの冒頭からの引用です。 どういう意味かは記事を読んでいただけると分かるのですが、まとめると、
- Rubyのように動的性質を持っていて、
- Cのように高速で、
- Lispのようにマクロもあって、
- Rのように統計が得意で、
- Perlのように文字列処理もできて、
- MATLABのように線形代数もできる
- シンプルでオープンでインタラクティブな言語がほしい
ということになります。そして、それを実現したのがJuliaです。
Juliaとは何か
Juliaの見た目を知るためにクイックソートのコード例を示します。
quicksort(xs::Vector) = quicksort!(copy(xs)) function quicksort!(xs::Vector, lo=1, hi=endof(xs)) if lo < hi p = partition(xs, lo, hi) quicksort!(xs, lo, p - 1) quicksort!(xs, p + 1, hi) end return xs end function partition(xs, lo, hi) pivot = div(lo + hi, 2) pvalue = xs[pivot] xs[pivot], xs[hi] = xs[hi], xs[pivot] j = lo @inbounds for i in lo:hi-1 if xs[i] <= pvalue xs[i], xs[j] = xs[j], xs[i] j += 1 end end xs[j], xs[hi] = xs[hi], xs[j] return j end
多くのスクリプト言語と共通するのは、
- 簡潔な文法 (MATLAB風)
- 変数の型宣言が不要
- 引数の型指定が不要
というところでしょう。 一方、あまり見られない特徴としては、
が挙げられます。 型推論とJITコンパイルで、変数の型が決定できるときには非常に高速なコードが生成されます。 マクロはLispに強い影響を受けていて、Cプリプロセッサのような文字列置き換えでなく、式を書き換えることができるものです。 コンパクトなデータ構造というのは、型定義の際にそのデータ構造が占める容量が決定でき、各メンバーへのポインターなど余計なサイズを取らないということです。
Juliaの書きやすさと表現力は、標準ライブラリに現れています。
Juliaの標準ライブラリはほぼすべてJuliaで書かれており、Cのライブラリの呼び出しもJuliaから簡単にできます。
そして、先ほどのクイックソートは、型指定などは無いにも関わらず、とても高速に動きます。 同じように書いたC言語並みの速度です。 さらに、動的にコードを生成しますので、例えば浮動小数点数でなく整数に対しても、コードを変更したりせず高速に動きます。
数値型
元々科学技術計算を目的として作られていますので、数値型が豊富です。
符号付き整数型は8bit, 16bit, 32bit, 64bit, 128bitがそれぞれ用意されており、
Int8
からInt128
までわかり易い名前がついています。
同様に、符号なしの整数はUint64
などと定義されています。
浮動小数点数も16bit, 32bit, 64bitがあり、Float64
などと表記されます。
他には複素数や有理数、任意精度の数値の型もあらかじめ用意されています。
線形代数
ベクトルや行列の積や線形方程式の解、LU分解やSVDなども標準ライブラリにあり、 実装はOpenBLASやLAPACKなどの高速な実装を呼び出しますので、パフォーマンスも十分です。
多重ディスパッチ
Juliaの根幹をなす重要な機能が多重ディスパッチです。 これは、引数の型の組み合わせにより実行されるメソッドが切り替わるものです。
例えば、標準ライブラリにあるsum
関数を考えると、
sum([1,2,3])
とsum(trues(4))
で異なる実装のメソッドが呼び出されます。
[1,2,3]
の型はVector{Int}
であり、trues(4)
の型はBitVector
で、両者で(高速な)和の計算方法は異なります。
そのため、多重ディスパッチを利用して、別々に実装しているわけです。
この時に注目して欲しいのは、引数の型が型推論により決定できる場合は、呼び出しのオーバーヘッドがない点です。
このような特徴から、標準ライブラリでも多重ディスパッチは多用されています。
マクロ
@show
や@assert
など、デバッグなどに便利はマクロが多数存在しています。
マクロはJuliaの式を実行前に書き換えることができるメタプログラミング機能です。
例えば、@show x
とするだけで、変数x
の値が、x => 42
のように変数名付きで表示されます。
これは、普通の関数には不可能な仕組みです。
コード生成
自分の書いた関数や、ライブラリのコードが実際にどのように実行されるかは、@code_llvm
と@code_native
マクロを使えば確認できます。
それぞれ、コンパイルしたLLVM IRとネイティブのコードを表示してくれるため、最適化などの際に非常に重要です。
Juliaは何処へ行くのか
Juliaのバージョンは、以下の様なものがあります。2015年7月末の時点では、v0.3が最新版です。
- v0.1 ← 未開の地
- v0.2 ← もはや郷愁の念
- v0.3 ← いまここ
- v0.4 ← もうすぐ
- v0.5 ← 来年・再来年あたり
- v1.0 ← 未定
v0.4の新機能は、こちらで確認できます。
julia/NEWS.md at master · JuliaLang/julia · GitHub
Ecosystem
現在600以上のパッケージがあり現在も増え続けています。
Conference
先月にはJuliaCon 2015もありました。実ははるばるボストンまで行ってきたのでした。
様々な分野の方々がJuliaを実際に使っていて、とてもよい印象を持っているようです。
強力なスポンサーもついており、ムーア財団やBlack Rock, Intelなどが出資しています。 JuliaConにもGoogle, Facebook, Intelといった会社の方々がたくさん来ていました。
Julia Computing
Juliaの創始者達が、スタートアップを始めたようです。 Juliaのコンサル業などをしており、Julia自体は今後もオープンであり続けるようです。
JuliaTokyo
我々はJuliaTokyoというユーザグループを立ち上げ、年に数回勉強会も開いています。 次回はまだ未定ですが、是非都合が合えば参加してみてください。
質疑
いくつか現地で質疑や雑談で話したことを簡単にまとめておきます。
- Q1. 去年Juliaを使ったとき(v0.2)、起動がとても遅かったが、いまはどうなのか。
A1. 最新リリース版のv0.3では、標準ライブラリにプリコンパイル機能が入ったので、起動時間は大幅に短縮された。さらに次期バージョン(v0.4)では、外部パッケージのプリコンパイル機能が入るので、パッケージの読み込みも高速化する。
Q2. Webなどのツールはどうなのか。
A2. JuliaWebグループがサーバなどの環境を整備している。Juliaは各種専門グループがいくつかあり、オープンにツールを開発している。
Q3. Juliaはどういう使われ方を想定しているのか。
A3. Juliaは汎用プログラミング言語になることを目指している。インターフェースからコアの計算までを一つの言語で書けることを目指しつつ、数値計算のみならずPythonのように汎用的に使われるようにしようとしている。また、Juliaを組み込んで使う方法もドキュメントに記載されており、そのように別のツールの内部で使われる方向もあると思う。
Q4. 何かJuliaには問題はないのか。
A4. パフォーマンス上の落とし穴がいくつかある。基本的には型推論がうまく動かないコードや、オブジェクト生成に関する誤解が原因。マニュアルにPerformance Tipsのセクションがあるので、読むと良い。
Q5. JuliaのIDEみたいなのがあった気がするが、どうしたのか。
A5. Julia Studioは死んだ。現在はLight Tableとうまく連携するJunoというツールがあるので、これを使うと良さそう。個人的にはvimを使っている。
Q6. プレゼンに使っているそのツールはなにか。
- A6. IPython Notebookというもの。ローカルでJuliaの処理系が動いており、ブラウザからコードを実行し結果を確認できる。最近JupyterというツールとしてPythonに依存しない機能が分離し、JuliaやPythonのみならず、RやRubyなど様々な言語も実行できる。
所感
割りと多くの方がJuliaに興味を持ってくれたようでした。 ウェブの企業だと、プログラミング言語自体の実行性能を気にすることはあまり無いようですが、分析系の人達は今のRなどに不満があって、速くなるのならJuliaを使ってみるのもいいかもしれないと言っていました。 Juliaのマニュアルの日本語化は需要があるようで、完成すれば普及に一役買うかもしれません。