Julia

前回からJuliaにも登場願ってる。最近PythonやRとの比較で、よくJuliaがあちこちに登場 するな。 どうすればPythonをJuliaと同じくらい速く動かせるのか? とか、意識されてるしね。 遅れないように再度、お勉強。資料は下記ぐらいかな。

X分で学ぶJulia

Basic Numerical Programming in Julia

advent-cal 2014

advent-cal 2015

SlideShare Julia

Juliaの起動は速いのか?

案内によるとv0.4になって速くなったとの事。本当か? ハロワの表示で確認。

[sakae@fedora plt]$ time julia t.jl
hellow

real    0m5.347s
user    0m0.295s
sys     0m0.637s
[sakae@fedora plt]$ time julia t.jl
hellow

real    0m0.583s
user    0m0.302s
sys     0m0.266s

一回目は、Fedora23の起動直後。バッファーキャッシュにjuliaが載っていないので、 Diskからエッチラコッチラとロードしてきて、リンカーがライブラリーを結合して、JITを 走らせてってやるから時間がかかる。2回目からは、キャッシュに載ってるんで、その 工程が省かれるんで速くなる。

バッファーキャッシュを任意の時点でクリアーする方法ってあるんだろうか? 調べたら、よく忘れるんでメモしてた方がおられた。 Fedora20: メモリキャッシュのクリア

[sakae@fedora plt]$ sudo sysctl -w vm.drop_caches=3
vm.drop_caches = 3
[sakae@fedora plt]$ time julia t.jl
hellow

real    0m5.446s
user    0m0.245s
sys     0m0.622s
[sakae@fedora plt]$ time julia t.jl
hellow

real    0m0.584s
user    0m0.294s
sys     0m0.275s

こちらは、メモリーの状態。

[sakae@fedora plt]$ free -h
              total        used        free      shared  buff/cache   available
Mem:           755M         77M        521M        1.0M        156M        646M
Swap:          1.2G          0B        1.2G
[sakae@fedora plt]$ sudo sysctl -w vm.drop_caches=3
vm.drop_caches = 3
[sakae@fedora plt]$ free -h
              total        used        free      shared  buff/cache   available
Mem:           755M         76M        571M        1.0M        107M        647M
Swap:          1.2G          0B        1.2G

隠れたRAM-Diskって趣きだな。威力あるよ。あれ? juliaって起動時にJITを働かせて いるの? 大きな数の素因数分解をやってみる。

[sakae@fedora plt]$ time julia t.jl
Dict(5=>1,3=>2,13=>1,2207=>1,751=>1)

real    0m7.208s
user    0m1.978s
sys     0m0.792s
[sakae@fedora plt]$ time julia t.jl
Dict(5=>1,3=>2,13=>1,2207=>1,751=>1)

real    0m2.756s
user    0m2.370s
sys     0m0.328s

ハロワの初回は5.5秒だったのが、コンパイルが難しそうなやつは、余計に時間がかかって いる。一度JITでコンパイルしたやつは、juliaがキャッシュしてくれれば良いと思われ。 そんな機構があればいいのに。

時間測定

かかった時間をjuliaの中で測定するには、@time系マクロで決定なんだけど、一つ不満がある。 手頃に測定するには、関数にするしかない。ここから、あそこまでを実行する時間を 測りたいって事も時には有るだろう。

そういう時の為にストップウォッチが用意されてる。ticでスタート。tocまたはtoqで、 かかった時間を表示。好きな所に埋め込めるぞ。

julia> tic()
0x000002bc1fc76c0a

julia> toc()                        ## or toq()
elapsed time: 3.988518331 seconds
3.988518331

edit

前回 editでemacsが起動しない不具合が有った。難で? こういう時はソース嫁。特徴的な 文字列、EDITOR を手がかりにbaseの下を探ってみると、interactiveutil.jl に答えは有った。

function editor()
    if OS_NAME == :Windows || OS_NAME == :Darwin
        default_editor = "open"
    elseif isreadable("/etc/alternatives/editor")
        default_editor = "/etc/alternatives/editor"
    else
        default_editor = "emacs"
    end
    # Note: the editor path can include spaces (if escaped) and flags.
    command = shell_split(get(ENV,"JULIA_EDITOR", get(ENV,"VISUAL", get(ENV,"EDITOR", default_editor))))
    isempty(command) && error("editor is empty")
    return command
end

使えそうなeditorをあぶり出し、無ければemacsを仮に指定。環境変数を見て、最終的に決定って 戦略。emacsのユーザーは何も設定しなくても採用されるとな。

function edit(file::AbstractString, line::Integer)
    command = editor()
    name = basename(first(command))
    issrc = length(file)>2 && file[end-2:end] == ".jl"
    if issrc
        f = find_source_file(file)
        f !== nothing && (file = f)
    end
    const no_line_msg = "Unknown editor: no line number information passed.\nThe method is defined at line $line."
    if startswith(name, "emacs") || name == "gedit"
        spawn(`$command +$line $file`)
    elseif name == "vi" || name == "vim" || name == "nvim" || name == "mvim" || name == "nano"
        run(`$command +$line $file`)
    elseif name == "textmate" || name == "mate" || name == "kate"
        spawn(`$command $file -l $line`)
    :

そして、決定したeditorによって起動方法を選んでる。 このコードを見てemacsが表示されない原因が分かった。GUI版のemacsを起動すんのね。でも、 おいらの環境ではたまたまWindows側にXmingを用意して無かったので、emacsの起動に失敗したとな。

原因が分かればXmingを面倒だけど起動しとくだけ。だが、背景が白いEmacsは嫌いってんで、何時も画面 反転の-rを付けて起動してる。このスイッチが指定出来ないんじゃ眼に悪い。しょうがないので、vimで色付きにするか。 julia-vim ああ、init.elでGUI環境か見て、 背景を黒くするってのは、オイラーの信義に反するからヤリません。

でもどうしても使い慣れたemacsが良いな。emacsを端末に貼り付けるaliasを使っているんで、 それをshファイルにして、nanoとかって名前にすれば、juliaを騙せる。が、そんな事をしたら、 オイラーの美的感覚が文句を言う。ここはどうしても、julia内で片付けてみろ。

#=
 This file is a part of Julia. License is MIT: http://julialang.org/license
 porting from interactiveutil.jl
 Usage: @em function or file
=#

import Base: find_in_path, find_source_file, function_module, functionloc

function emacs(file::AbstractString, line::Integer)
    issrc = length(file)>2 && file[end-2:end] == ".jl"
    if issrc
        f = find_source_file(file)
        f !== nothing && (file = f)
    end
    run(`emacsclient -nw -a "" +$line $file`)
    nothing
end

function emacs(m::Method)
    tv, decls, file, line = arg_decl_parts(m)
    emacs(string(file), line)
end

emacs(file::AbstractString) = emacs(file, 1)
emacs(f)          = emacs(functionloc(f)...)
emacs(f, t::ANY)  = emacs(functionloc(f,t)...)
emacs(file, line::Integer) = emacs(file, line)

macro em(f)
    return :( emacs($f) )
end

オリジナルのコードを引っ張ってきて、editをemacsに改名し整理した。 マルチメソッドってclojureやCフラフラの専売特許かと思ったら、juliaでもちゃんと 使われているね。

後は、このコードを、~/.juliarc.jl としておけば、起動直後からemacsで編集出来る。 一応、使い方

julia> @em "base64.jl"

julia> @em factor

julia> @em find
ERROR: function has multiple methods; please specify a type signature
 in functionloc at reflection.jl:334
 in emacs at /home/sakae/.juliarc.jl:25

julia> methods(find)
# 6 methods for generic function "find":
find(testf::Function, A::AbstractArray{T,N}) at array.jl:765
find(B::BitArray{N}) at bitarray.jl:1420
find(A::Union{DenseArray{T,N},SubArray{T,N,A<:DenseArray{T,N},I<:Tuple{Vararg{Union{Colon,Int32,Range{Int32}}}},LD}}) at array.jl:777
find(x::Number) at array.jl:789
find(testf::Function, x::Number) at array.jl:790
find(S::SparseMatrixCSC{Tv,Ti<:Integer}) at sparse/sparsematrix.jl:407

julia> emacs(find,(Number,))

ファイルや普通のfunctionは問題ないけど、マルチメソッドはエラーになる。しょうがないので、どんなのが あるか、あらかじめ調べてから、emacs形式で呼ぶ。

interactiveutil.jl から発見せしもの

コードをブラウジングしてると、いろいろな物に当る。犬も歩けば棒に当るってね。 面白いものを発見。

julia> versioninfo()
Julia Version 0.4.3
Commit a2f713d* (2016-01-12 21:37 UTC)
Platform Info:
  System: Linux (i686-redhat-linux)
  CPU: Intel(R) Celeron(R) CPU          900  @ 2.20GHz
  WORD_SIZE: 32
  BLAS: libopenblas (DYNAMIC_ARCH NO_AFFINITY Penryn)
  LAPACK: libopenblasp.so.0
  LIBM: libopenlibm
  LLVM: libLLVM-3.3

この結果を出すのは恥ずかしいぐらい旧式の石だな。だから旧石器時代って言うんだよ。 何時頃か年代鑑定したら、2009年の暮れに購入って 記録が出てきた。丸6年の酷使にもかかわらずに無傷で動いております。

julia> whos()
                           @em    181 bytes  Function
                          Base  19261 KB     Module
                          Core   2223 KB     Module
                          Main  21301 KB     Module
                           ans     19 bytes  ASCIIString
                         emacs   2607 bytes  Function

そして、これが現在のreplの状況とな。ansってreplでの最後の結果をbindしてるんだな。

v0.4

大してJuliaを使っている訳でもないのに偉そうに、 Julia v0.4 新機能\&変更点まとめ なんてのを見ています。

で、気になったのがモジュールのコンパイル機能。この際だからGastonの盛大な、 ここ直せを直しましたよ。

WARNING: Base.String is deprecated, use AbstractString instead.
  likely near /home/sakae/.julia/v0.4/Gaston/src/gaston_config.jl:173

そして、

__precompile__()

をGaston.jlのmojule宣言の前に追加。

[sakae@fedora ~]$ julia -e 'tic(); using Gaston; toc()'
elapsed time: 2.846259885 seconds
[sakae@fedora ~]$ julia -e 'tic(); using Gaston; toc()'
elapsed time: 10.55525993 seconds
[sakae@fedora ~]$ julia -e 'tic(); using Gaston; toc()'
elapsed time: 0.798265587 seconds

上段は、コンパイル指定せず。中段はプリコンパイル中、下段でコンパイル済みを使って くれたので、ロードが速くなった。これ便利だな。

lazy

クリスマス日記に、 Julia の Macro と Iteration の実験 なんてのが掲載されてた。Macroでピピーンと反応するのは、(隠れ)Lisperの性。

題材はLazyですって。HaskellerをJuliaに誘おうって戦略だな。当然、juliaが数学屋の 道具なら、これは必須ですよ。

サンプルコードを見ていて、juliaにもtakeとかdropなんてのが有る事を知る。何処に置いて あるかと思ったら、iterator.jlだった。

# Take -- iterate through the first n elements

immutable Take{I}
    xs::I
    n::Int
end
take(xs, n::Int) = Take(xs, n)

eltype{I}(::Type{Take{I}}) = eltype(I)

start(it::Take) = (it.n, start(it.xs))

function next(it::Take, state)
    n, xs_state = state
    v, xs_state = next(it.xs, xs_state)
    return v, (n - 1, xs_state)
end

function done(it::Take, state)
    n, xs_state = state
    return n <= 0 || done(it.xs, xs_state)
end

startとnextとdoneを実装するとTakeを実現出来るとな。面白い、実に面白い! こういう 考え方も有ったのか。

そう言えば、以前にHaskellで血圧を月毎に集計するコードを書いた。あれも、今にして 振り返れば、start,next,doneから出来上がっていたな。まあ、あの時はそういう意識は 無かったけど、知らず知らずのうちに、そう実現してた。関数脳に冒されてしまって、 もう修復困難なのだろうな。

Debug

debuggerもPkgに有る。どうやって実現してる? 興味は尽きない。

[sakae@fedora examples]$ julia test.jl
Commands:
--------
h: display this help text
s: step into
n: step over any enclosed scope
o: step out from the current scope
c: continue to next breakpoint
l [n]: list n source lines above and below current line (default n = 3)
p cmd: print cmd evaluated in current scope
q: quit debug session (calls error("interrupted"))
To e.g. evaluate the variable named `n`, enter it as ` n` (with a space).

Debug variables:
---------------
$n:    current node
$s:    current scope
$bp:   Set{Node} of enabled breakpoints
$nobp: Set{Node} of disabled @bp breakpoints
$pre:  Dict{Node} of grafts

Example usage:
-------------
$(push!(bp, n))      # set breakpoint at the current node
$(delete!(bp, n))    # unset breakpoint at the current node
$(push!(nobp, n))    # ignore @bp breakpoint at the current node
$(pre[n] = :(x = 0)) # execute x=0 just before the current node, at each visit

Type an expression to evaluate it in the current scope.

at /home/sakae/.julia/v0.4/Debug/examples/test.jl:12

      11           x, y = 0, 1
 -->  12           @bp
      13           go_on = true

debug:12>

macro

(隠れ)Lisperとしては、macroは基本素養。julia流を身に付けておきましょ。

macro time(ex)
    quote
        local stats = gc_num()
        local elapsedtime = time_ns()
        local val = $(esc(ex))
        elapsedtime = time_ns() - elapsedtime
        local diff = GC_Diff(gc_num(), stats)
        time_print(elapsedtime, diff.allocd, diff.total_time,
                   gc_alloc_count(diff))
        val
    end
end

etags *.jl

上で作ったemacsメソッドを使って、ソースの海を泳ぎ出すと、引っかからないfunctionが 出てくる。例えば、

function whos(io::IO=STDOUT, m::Module=current_module(), pattern::Regex=r"")
    maxline = tty_size()[2]
    line = zeros(UInt8, maxline)
      :

の、tty_size()とかだ。これはもうetagsの出番? でも、juliaなんて言う新興の言語は 対応していない。manすると、--regexで、指定出来るよなんて事が書いてある。 随分前から脳力が衰えていて、正規表現はすっかり嫌いになってる。

折角juliaに巡り遭ったのだから、juliaを使ってetagsと同じ事をさせるって手を思い付いた。 どんなフォーマットになってるの? Ctags と言いつつ、etagsのフォーマットも解説されてた。

どうやら、正規表現は避けて通れそうにない。残念、頑張って脳力を付けるんじゃ、と天の 声が聞こえてきた。

諦めきれないオイラーは、もう一度etagsがサポートしてる対象言語を眺めた。そして、はたと 気が付いた。luaとかJSの関数指定って、functionじゃなかったっけ?(うろ覚えもはなはだしい!)

やってみれ。ひょっとしたら、関数だけは拾ってくれるのではなかろうか。正解でした。 ちゃんと、上のtty_sizeを見つけて、飛んで行ってくれたよ。

ちなみに

sakae@uB:/usr/share/julia/base$ grep function *.jl */*.jl | wc -l
4493
sakae@uB:/usr/share/julia/base$ wc -l TAGS
4442 TAGS

大体、網羅してるから良しとしよう。ああ、一応証拠の提示。

sakae@uB:/usr/share/julia/base$ grep tty_size -B 15 TAGS
function membershiptest(15,389
        function Base.Base78,2508
        function Base.Base88,3132

env.jl,303
function _jl_win_getenv(12,512
function _setenv(45,1458
function _unsetenv(61,2008
function delete!delete84,2843
function next(100,3392
function done(116,3858
function next(123,4078
function length(139,4585
function show(147,4684
function withenv{withenv154,4835
function tty_size(171,5317

ふと、ソースのtar玉の中にあるcontrib内のctagsを覗いたら、恐ろしい事が 書いてあったぞ。

--langdef=julia
--langmap=julia:.jl
--regex-julia=/^[ \t]*(function|macro|abstract|type|typealias|immutable)[ \t]+([^ \t({[]+).*$/\2/f,function/
--regex-julia=/^[ \t]*(([^@#$ \t({[]+)|\(([^@#$ \t({[]+)\)|\((\$)\))[ \t]*(\{.*\})?[ \t]*\([^#]*\)[ \t]*=([^=].*$|$)/\2\3\4/f,function/

頭が痛くなるわい。