本日は
最近,社内で(外部の方を含めた)Juliaもくもく会を企画しました.
社内では私一人です.本当にありがとうございます.
最近自然発生的に野生のカン(?)で docker-compose を覚えたのでJuliaでも使える場面ないかなーと模索してました.
Dockerが使えると何が嬉しいの?
Docker を使うと古いJulia バージョンが 0.X.Y の物を動かすことができます.
例えば手元に src.jl
というスクリプトがあってそれが Julia 0.5.1 の時代の物でしか動かないとします.すでに Julia のメジャーバージョンは1になっておりもう少しで 1.3がでる・でないの境目にいますので大抵の人はローカルの環境に Julia1.0.5 とか Julia 1.2 を入れてるでしょう.
そんな時に急に Julia 0.5.1 のものを動かす必要に迫られたとします.逆に Julia 1.0.5 の環境で生活しているけれど突然 Julia 1.2 のものを動かしたくなったとかもあるかもしれませんね.Dockerの場合コンテナを立ち上げることでその中で特定のJuliaの環境を作り,その中(コンテナ内)で実行させることができます.例えば VERSION
という,今動かしているJuliaのバージョン情報が格納されているオブジェクトを出力させる例で試してみましょう.Dockerの環境がない場合はまずDockerの公式ホームページにアクセスしWindowsなりMacなりLinuxの環境なりで合わせて導入する必要があります.
$ cat src.jl
@show VERSION
$ docker run --rm -it -v $PWD:/work -w /work julia:0.5.1 julia src.jl
# 初回はイメージをダウンロードする作業が始まる
# 出力は下記のようになる
VERSION = v"0.5.1"
# もう一回同じことをする
$ docker run --rm -it -v $PWD:/work -w /work julia:0.5.1 julia src.jl
# すぐ実行できる
VERSION = v"0.5.1"
# Julia 1.1.1 の環境で動かしてみたい
$ docker run --rm -it -v $PWD:/work -w /work julia:1.2.0 julia src.jl
# 0.5.1 の場合と同じように初回はイメージをダウンロードする
VERSION = v"1.2.0"
このようにして Docker を用意しておけば普段使っていないバージョンのJuliaを気軽に試すことができます.
ここでは詳しく書きませんが,Dockerfile
という環境構築の手順を記述したファイルを用意しておけば既存のイメージから各々の好きな環境を構築することができます.これで自分の使っている環境を別の人に共有することができ,「aptでこれとこれとこれを入れてからJuliaのパッケージA,BそしてCを入れてねー」と伝える代わりに「この Dockerfile
をビルドしてね」というだけですみます.便利でしょ?(ポエム)
使うことある?
- 一番プラクティカルなのはAtCoderの問題をJuliaで解く場合に活躍するでしょう.今, 現在ですと Julia は 0.5.0 の環境で動かすことが要請されています.今後はどうなるかは不明ですがいつかはJulia1.xの環境でできると思われます.とりあえずAtCoder Beginners Selection などの問題をカジュアルに解いてみたい時に提出前の動作確認をするための環境構築として役立つと思います.
- 例えば 入力が格納されたテキストファイルとして
inp.txt
であるとし書いたコードがsrc.jl
の場合を考えます.
1 2 3
4 5 6
inp1=readline()
inp2=readline()
# 文字列を数字として認識させたい
arr1=parse.(split(inp1))
arr2=parse.(split(inp2))
# 何かをする
@show arr1+arr2
これを julia 0.5.0 の環境をもつDockerでするには次のようにします.
$ docker run --rm -it -v $PWD:/work -w /work julia:0.5.0 bash -c "julia src.jl < inp.txt"
arr1 + arr2 = [5,7,9]
が出ればOKです.これでJuliaでの競技プログラミングも捗るんではないでしょうか?
もしかして異なるJuilaで動作をみる場合,毎回コマンドを打たなければいけない?
今手元にあるJuliaのコードが 各々のバージョンで動くか確認したいとします(そんなことそもそもあるんだろうか?は置いておいて・・・).毎回
docker run --rm -it -v $PWD:/work -w /work julia:x.y.z bash -c "julia src.jl < inp.txt"
を打つのは面倒なわけです.そんな時は docker-compose を使えば多少は回避できます.docker-compose は複数のコンテナを使う Docker アプリケーションを、定義・実行するツールなのですので使ってみましょう.(あれですね,このツールを使うためにxxxをするという目的が見失ってる奴ですね.ポエムなので許してください)
docker-compose.yml を用意
ひとまず下記のようなファイルを作成します.
# This is a tool that checks whether your Julia script src.jl works fine for each version of Julia or not.
# Usage:
# write src.jl
# write inp.txt
# just run docker-compose up
version: '3'
services:
julia_0.5.0:
image: julia:0.5.0
container_name: julia_0.5.0
volumes:
- ./:/work
working_dir: /work
command: /bin/bash -c "julia src.jl < inp.txt"
julia_0.5.1:
image: julia:0.5.1
container_name: julia_0.5.1
volumes:
- ./:/work
working_dir: /work
command: /bin/bash -c "julia src.jl < inp.txt"
julia_0.6.1:
image: julia:0.6.1
container_name: julia_0.6.1
volumes:
- ./:/work
working_dir: /work
command: /bin/bash -c "julia src.jl < inp.txt"
julia_1.0.5:
image: julia:1.0.5
container_name: julia_1.0.5
volumes:
- ./:/work
working_dir: /work
command: /bin/bash -c "julia src.jl < inp.txt"
julia_1.2.0:
image: julia:1.2.0
container_name: julia_1.2.0
volumes:
- ./:/work
working_dir: /work
command: /bin/bash -c "julia src.jl < inp.txt"
上で述べた競技プログラミングの例の続きとして src.jl
, inp.txt
がdocker-compose.yml
と同じ階層にあるとします.そこで docker-compose up
を実行します.docker-compose
はコマンドです.
$ ls
docker-compose.yml inp.txt src.jl
$ docker-compose up
Starting julia_1.0.5 ... done
Starting julia_1.2.0 ... done
Starting julia_0.5.1 ... done
Starting julia_0.6.1 ... done
Starting julia_0.5.0 ... done
Attaching to julia_0.6.1, julia_1.2.0, julia_0.5.1, julia_1.0.5, julia_0.5.0
julia_0.5.1 | arr1 + arr2 = [5,7,9]
julia_0.5.0 | arr1 + arr2 = [5,7,9]
julia_1.2.0 | ERROR: LoadError: MethodError: no method matching parse(::SubString{String})
julia_1.2.0 | Closest candidates are:
julia_1.2.0 | parse(!Matched::Type{T<:Integer}, !Matched::AbstractChar; base) where T<:Integer at parse.jl:41
julia_1.2.0 | parse(!Matched::Type{T<:Integer}, !Matched::AbstractString; base) where T<:Integer at parse.jl:240
julia_1.2.0 | parse(!Matched::Type{T<:Real}, !Matched::AbstractString; kwargs...) where T<:Real at parse.jl:378
julia_1.0.5 | ERROR: LoadError: MethodError: no method matching parse(::SubString{String})
julia_1.0.5 | Closest candidates are:
julia_1.0.5 | parse(!Matched::Type{T<:Integer}, !Matched::AbstractChar; base) where T<:Integer at parse.jl:38
julia_1.0.5 | parse(!Matched::Type{LibGit2.GitCredential}, !Matched::AbstractString) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.0/LibGit2/src/gitcredential.jl:73
julia_1.0.5 | parse(!Matched::Type{LibGit2.GitCredentialHelper}, !Matched::AbstractString) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.0/LibGit2/src/gitcredential.jl:163
julia_1.0.5 | ...
julia_1.2.0 | ...
julia_1.0.5 | Stacktrace:
julia_1.2.0 | Stacktrace:
julia_1.0.5 | [1] _broadcast_getindex_evalf at ./broadcast.jl:582 [inlined]
julia_1.0.5 | [2] _broadcast_getindex at ./broadcast.jl:555 [inlined]
julia_1.0.5 | [3] getindex at ./broadcast.jl:515 [inlined]
julia_1.0.5 | [4] copy at ./broadcast.jl:790 [inlined]
julia_1.2.0 | [1] _broadcast_getindex_evalf at ./broadcast.jl:625 [inlined]
julia_1.2.0 | [2] _broadcast_getindex at ./broadcast.jl:598 [inlined]
julia_1.2.0 | [3] getindex at ./broadcast.jl:558 [inlined]
julia_1.2.0 | [4] copy at ./broadcast.jl:832 [inlined]
julia_1.0.5 | [5] materialize(::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1},Nothing,typeof(parse),Tuple{Array{SubString{String},1}}}) at ./broadcast.jl:756
julia_1.0.5 | [6] top-level scope at none:0
julia_1.0.5 | [7] include at ./boot.jl:317 [inlined]
julia_1.0.5 | [8] include_relative(::Module, ::String) at ./loading.jl:1044
julia_1.0.5 | [9] include(::Module, ::String) at ./sysimg.jl:29
julia_1.0.5 | [10] exec_options(::Base.JLOptions) at ./client.jl:266
julia_1.0.5 | [11] _start() at ./client.jl:425
julia_1.0.5 | in expression starting at /work/src.jl:5
julia_1.2.0 | [5] materialize(::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1},Nothing,typeof(parse),Tuple{Array{SubString{String},1}}}) at ./broadcast.jl:798
julia_1.2.0 | [6] top-level scope at /work/src.jl:5
julia_1.2.0 | [7] include at ./boot.jl:328 [inlined]
julia_1.2.0 | [8] include_relative(::Module, ::String) at ./loading.jl:1094
julia_1.2.0 | [9] include(::Module, ::String) at ./Base.jl:31
julia_1.2.0 | [10] exec_options(::Base.JLOptions) at ./client.jl:295
julia_1.2.0 | [11] _start() at ./client.jl:464
julia_1.2.0 | in expression starting at /work/src.jl:5
julia_0.6.1 | arr1 + arr2 = [5, 7, 9]
julia_0.5.1 exited with code 0
julia_0.5.0 exited with code 0
julia_1.0.5 exited with code 1
julia_1.2.0 exited with code 1
julia_0.6.1 exited with code 0
docker-compose up
をすることで各バージョンのJuliaが入っているコンテナを一気起動させて各々のバージョンで同じスクリプトを動かすことができるようになりました.出力結果をよく確認すると Juliaの0.y.z の環境では動作が無事終了し, 1.y.zの環境では失敗していることがわかりますね.こんな感じで docker-compose up
を使うこともできます. ちなみに後片付けは docker-compose down
でOKです.
こうしてdocker-composeをJuliaで使う応用例ができました(白目)
Jupyter で動かしたい人向け
# Usage
# $ docker build -t jp .
# $ docker run --rm -it -p 8888:8888 jp jupyter notebook --NotebookApp.token='' --ip=0.0.0.0 --allow-root
# Open Web browser and access
# http://localhost:8888
from julia:1.2.0
# install python env and connect with PyCall.jl
RUN julia -e 'ENV["PYTHON"]="";\
using Pkg; Pkg.add(["PyCall", "Conda"]); \
using Conda; Conda.add("python=3.6.*"); \
Pkg.build("PyCall"); using PyCall'
# enable to call python and jupyter from bash
ENV PATH="/root/.julia/conda/3/bin:$PATH:${PATH}"
RUN julia -e 'using Pkg, Conda;\
Conda.add(["jupyter"]);\
Pkg.add(["IJulia"]);\
using IJulia'
EXPOSE 8888
おしまい.