最新Juliaチュートリアル

2017/08/22 @JuliaTokyo #7 (https://juliatokyo.connpass.com/event/62233/)

今日の話

概要

  • Juliaとは?
  • Juliaの基礎知識
  • Juliaの実行モデル
  • 今後の動向

目的

  • 構文や基本的な機能を理解する
  • 実行の仕方を理解し、高速なプログラムが書けるようになる
  • 最新の動向にキャッチアップする

自己紹介

Juliaとは?

科学技術計算を目的として作られた動的プログラミング言語

  • 数値計算用の豊富な標準ライブラリ
  • forループが超速い
  • 平易な構文・シンプルな言語機能
  • CやFortranとの連携が容易
  • オープンソース (MITライセンス)

http://julialang.org

Hello, PCA!

In [1]:
function pca(X)
     = X .- mean(X, 1)
    U, Σ, V = svd()
    return  * V
end
Out[1]:
pca (generic function with 1 method)
In [2]:
iris = readcsv("iris.csv", header=true)[1]
X = convert(Matrix{Float64}, iris[:,1:4])
Out[2]:
150×4 Array{Float64,2}:
 5.1  3.5  1.4  0.2
 4.9  3.0  1.4  0.2
 4.7  3.2  1.3  0.2
 4.6  3.1  1.5  0.2
 5.0  3.6  1.4  0.2
 5.4  3.9  1.7  0.4
 4.6  3.4  1.4  0.3
 5.0  3.4  1.5  0.2
 4.4  2.9  1.4  0.2
 4.9  3.1  1.5  0.1
 5.4  3.7  1.5  0.2
 4.8  3.4  1.6  0.2
 4.8  3.0  1.4  0.1
 ⋮                 
 6.0  3.0  4.8  1.8
 6.9  3.1  5.4  2.1
 6.7  3.1  5.6  2.4
 6.9  3.1  5.1  2.3
 5.8  2.7  5.1  1.9
 6.8  3.2  5.9  2.3
 6.7  3.3  5.7  2.5
 6.7  3.0  5.2  2.3
 6.3  2.5  5.0  1.9
 6.5  3.0  5.2  2.0
 6.2  3.4  5.4  2.3
 5.9  3.0  5.1  1.8
In [3]:
X_pca = pca(X)
Out[3]:
150×4 Array{Float64,2}:
 -2.68413  -0.319397    0.0279148   0.00226244
 -2.71414   0.177001    0.210464    0.0990266 
 -2.88899   0.144949   -0.0179003   0.0199684 
 -2.74534   0.318299   -0.0315594  -0.0755758 
 -2.72872  -0.326755   -0.0900792  -0.0612586 
 -2.28086  -0.74133    -0.168678   -0.0242009 
 -2.82054   0.0894614  -0.257892   -0.0481431 
 -2.62614  -0.163385    0.0218793  -0.0452979 
 -2.88638   0.578312   -0.0207596  -0.0267447 
 -2.67276   0.113774    0.197633   -0.0562954 
 -2.50695  -0.645069    0.075318   -0.0150199 
 -2.61276  -0.0147299  -0.10215    -0.156379  
 -2.78611   0.235112    0.206844   -0.00788791
  ⋮                                           
  1.16933   0.16499    -0.281836    0.0204618 
  2.10761  -0.372288   -0.0272911   0.210622  
  2.31415  -0.183651   -0.322694    0.277654  
  1.92227  -0.409203   -0.113587    0.505305  
  1.41524   0.574916   -0.296323   -0.0153047 
  2.56301  -0.277863   -0.29257     0.0579127 
  2.41875  -0.304798   -0.504483    0.241091  
  1.94411  -0.187532   -0.177825    0.426196  
  1.52717   0.375317    0.121898    0.254367  
  1.76435  -0.0788589  -0.130482    0.137001  
  1.90094  -0.116628   -0.723252    0.0445953 
  1.39019   0.282661   -0.36291    -0.155039  
In [4]:
using PyPlot
species = iris[:,5]
cmap = Dict(s => "C$(i-1)" for (i, s) in enumerate(unique(species)))
scatter(X_pca[:,1], X_pca[:,2], c=getindex.(cmap, species));

数値計算への強さ

多次元配列を標準搭載

  • 任意次元の配列
  • ブロードキャスト
  • 平均・分散・中央値など統計量の計算関数

高速な数値計算ライブラリを標準搭載

  • OpenBLAS: 各アーキテクチャに最適化された行列計算ライブラリ
  • LAPACK: 行列分解など線型方程式を扱うライブラリ
  • DSFMT: 高速なメルセンヌ・ツイスター擬似乱生成器
In [5]:
srand(1234);
In [6]:
rand(3)  # 1次元配列
Out[6]:
3-element Array{Float64,1}:
 0.590845
 0.766797
 0.566237
In [7]:
rand(3, 3)  # 2次元配列
Out[7]:
3×3 Array{Float64,2}:
 0.460085  0.200586  0.579672 
 0.794026  0.298614  0.648882 
 0.854147  0.246837  0.0109059
In [8]:
rand(3, 3, 3)  # 3次元配列
Out[8]:
3×3×3 Array{Float64,3}:
[:, :, 1] =
 0.066423  0.112486  0.0566425
 0.956753  0.276021  0.842714 
 0.646691  0.651664  0.950498 

[:, :, 2] =
 0.96467   0.82116    0.314926
 0.945775  0.0341601  0.12781 
 0.789904  0.0945445  0.374187

[:, :, 3] =
 0.931115  0.0118196  0.732   
 0.438939  0.0460428  0.299058
 0.246862  0.496169   0.449182
In [9]:
A = randn(4, 2)
B = randn(2, 3)
A * B  # 行列積
Out[9]:
4×3 Array{Float64,2}:
 0.9481     1.22519   1.05156 
 1.40751    1.71371   0.97805 
 0.561272   0.733818  0.669705
 0.0696456  0.112251  0.200629
In [10]:
rank(A * B)
Out[10]:
2
In [11]:
U, Σ, V = svd(A * B);  # 特異値分解
In [12]:
U
Out[12]:
4×3 Array{Float64,2}:
 -0.571725    0.532279   0.619488
 -0.740511   -0.643106  -0.146947
 -0.347219    0.4311    -0.621254
 -0.0649046   0.342412  -0.45682 
In [13]:
Σ
Out[13]:
3-element Array{Float64,1}:
 3.26033    
 0.332009   
 2.64666e-16
In [14]:
V
Out[14]:
3×3 Array{Float64,2}:
 -0.547103  -0.405747   0.732153
 -0.684464  -0.286656  -0.670326
 -0.481859   0.86787    0.120889

欧米を中心に、数値計算の授業でもJuliaが使われている: https://julialang.org/teaching/

forループも速い

In [15]:
function add(x, y)
    output = similar(x)
    @inbounds for i in 1:endof(x)
        output[i] = x[i] + y[i]
    end
    return output
end
Out[15]:
add (generic function with 1 method)
In [16]:
x = randn(100_000);
y = randn(100_000);
In [17]:
x + y
for _ in 1:5
    @time x + y
end
  0.008612 seconds (2 allocations: 781.328 KiB, 92.76% gc time)
  0.000611 seconds (2 allocations: 781.328 KiB)
  0.000707 seconds (2 allocations: 781.328 KiB)
  0.000664 seconds (2 allocations: 781.328 KiB)
  0.000565 seconds (2 allocations: 781.328 KiB)
In [18]:
add(x, y)
for _ in 1:5
    @time add(x, y)
end
  0.000556 seconds (2 allocations: 781.328 KiB)
  0.000637 seconds (2 allocations: 781.328 KiB)
  0.000612 seconds (2 allocations: 781.328 KiB)
  0.000671 seconds (2 allocations: 781.328 KiB)
  0.000528 seconds (2 allocations: 781.328 KiB)
In [19]:
using PyCall
In [20]:
PyCall.pyversion
Out[20]:
v"3.5.2"
In [21]:
py"""
import numpy as np

def add(x, y):
    output = np.empty(x.shape)
    for i in range(len(x)):
        output[i] = x[i] + y[i]
    return output
"""
In [22]:
@time py"add($(x), $(y))"o;
  0.047117 seconds (2.01 k allocations: 115.593 KiB)
In [23]:
using BenchmarkTools
In [24]:
versioninfo()
Julia Version 0.6.0
Commit 903644385b* (2017-06-19 13:05 UTC)
Platform Info:
  OS: macOS (x86_64-apple-darwin16.6.0)
  CPU: Intel(R) Core(TM) i5-6267U CPU @ 2.90GHz
  WORD_SIZE: 64
  BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Haswell)
  LAPACK: libopenblas64_
  LIBM: libopenlibm
  LLVM: libLLVM-3.9.1 (ORCJIT, skylake)
In [25]:
b1 = @benchmark x + y
Out[25]:
BenchmarkTools.Trial: 
  memory estimate:  781.33 KiB
  allocs estimate:  2
  --------------
  minimum time:     71.093 μs (0.00% GC)
  median time:      126.816 μs (0.00% GC)
  mean time:        380.584 μs (21.48% GC)
  maximum time:     16.920 ms (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     1
In [26]:
# FLOPS
100_000 / (minimum(b1).time / 10^9)
Out[26]:
1.4066082455375352e9
In [27]:
b2 = @benchmark add(x, y)
Out[27]:
BenchmarkTools.Trial: 
  memory estimate:  781.33 KiB
  allocs estimate:  2
  --------------
  minimum time:     70.281 μs (0.00% GC)
  median time:      99.873 μs (0.00% GC)
  mean time:        318.488 μs (20.56% GC)
  maximum time:     7.014 ms (86.54% GC)
  --------------
  samples:          10000
  evals/sample:     1
In [28]:
# FLOPS
100_000 / (minimum(b2).time / 10^9)
Out[28]:
1.4228596633514037e9
In [29]:
judge(minimum(b1), minimum(b2))
Out[29]:
BenchmarkTools.TrialJudgement: 
  time:   +1.16% => invariant (5.00% tolerance)
  memory: +0.00% => invariant (1.00% tolerance)

速さの秘密

主に2つの要素

  • 型推論による実行時の曖昧性の排除
  • LLVMによるネイティブコードへの変換

これらにより他の動的言語では実現が難しいほどの最適化ができる

add(x, y)の型推論の結果

In [30]:
@code_typed add(x, y)
Out[30]:
CodeInfo(:(begin 
        $(Expr(:inbounds, false))
        # meta: location array.jl similar 189
        SSAValue(3) = (Base.arraysize)(x, 1)::Int64
        # meta: pop location
        $(Expr(:inbounds, :pop))
        output = $(Expr(:foreigncall, :(:jl_alloc_array_1d), Array{Float64,1}, svec(Any, Int64), Array{Float64,1}, 0, SSAValue(3), 0)) # line 3:
        $(Expr(:inbounds, true))
        $(Expr(:inbounds, false))
        # meta: location abstractarray.jl endof 134
        $(Expr(:inbounds, false))
        # meta: location abstractarray.jl linearindices 99
        # meta: location abstractarray.jl indices1 71
        # meta: location abstractarray.jl indices 64
        SSAValue(7) = (Base.arraysize)(x, 1)::Int64
        # meta: pop location
        # meta: pop location
        # meta: pop location
        $(Expr(:inbounds, :pop))
        # meta: pop location
        $(Expr(:inbounds, :pop))
        SSAValue(8) = (Base.select_value)((Base.slt_int)(SSAValue(7), 0)::Bool, 0, SSAValue(7))::Int64
        SSAValue(9) = (Base.select_value)((Base.sle_int)(1, SSAValue(8))::Bool, SSAValue(8), (Base.sub_int)(1, 1)::Int64)::Int64
        #temp# = 1
        25: 
        unless (Base.not_int)((#temp# === (Base.add_int)(SSAValue(9), 1)::Int64)::Bool)::Bool goto 36
        SSAValue(10) = #temp#
        SSAValue(11) = (Base.add_int)(#temp#, 1)::Int64
        i = SSAValue(10)
        #temp# = SSAValue(11) # line 4:
        SSAValue(2) = (Base.add_float)((Base.arrayref)(x, i)::Float64, (Base.arrayref)(y, i)::Float64)::Float64
        (Base.arrayset)(output, SSAValue(2), i)::Array{Float64,1}
        34: 
        goto 25
        36: 
        $(Expr(:inbounds, :pop)) # line 6:
        return output
    end))=>Array{Float64,1}

LLVMによるネイティブコードへの変換

In [31]:
@code_native add(x, y)
	.section	__TEXT,__text,regular,pure_instructions
Filename: In[15]
	pushq	%rbp
	movq	%rsp, %rbp
	pushq	%r15
	pushq	%r14
	pushq	%rbx
	subq	$40, %rsp
	movq	%rsi, %r15
	movq	%rdi, %rbx
	movabsq	$jl_get_ptls_states_fast, %rax
	callq	*%rax
	movq	%rax, %r14
	vxorpd	%xmm0, %xmm0, %xmm0
	vmovupd	%xmm0, -40(%rbp)
	movq	$4, -56(%rbp)
	movq	(%r14), %rax
	movq	%rax, -48(%rbp)
	leaq	-56(%rbp), %rax
	movq	%rax, (%r14)
Source line: 189
	movq	24(%rbx), %rsi
Source line: 2
	movabsq	$jl_alloc_array_1d, %rax
	leaq	99153040(%rax), %rdi
	callq	*%rax
	movq	%rax, -40(%rbp)
Source line: 64
	movq	24(%rbx), %rcx
Source line: 3
	testq	%rcx, %rcx
	jle	L354
Source line: 4
	movq	(%rbx), %r9
	movq	(%r15), %r10
	movq	(%rax), %r11
	movl	$1, %r15d
Source line: 3
	cmpq	$8, %rcx
	jb	L290
	movq	%rcx, %r8
	movl	$1, %r15d
	andq	$-8, %r8
	je	L290
	leaq	(%r11,%rcx,8), %rsi
	leaq	(%r9,%rcx,8), %rdx
	leaq	(%r10,%rcx,8), %rdi
	cmpq	%rdx, %r11
	sbbb	%dl, %dl
	cmpq	%rsi, %r9
	sbbb	%bl, %bl
	andb	%dl, %bl
	cmpq	%rdi, %r11
	sbbb	%dl, %dl
	cmpq	%rsi, %r10
	sbbb	%sil, %sil
	movl	$1, %r15d
	testb	$1, %bl
	jne	L290
	andb	%sil, %dl
	andb	$1, %dl
	jne	L290
	movq	%r8, %r15
	orq	$1, %r15
	leaq	32(%r9), %rdx
	leaq	32(%r10), %rsi
	leaq	32(%r11), %rdi
	movq	%r8, %rbx
	nopw	%cs:(%rax,%rax)
Source line: 4
L240:
	vmovupd	-32(%rdx), %ymm0
	vmovupd	(%rdx), %ymm1
	vaddpd	-32(%rsi), %ymm0, %ymm0
	vaddpd	(%rsi), %ymm1, %ymm1
	vmovupd	%ymm0, -32(%rdi)
	vmovupd	%ymm1, (%rdi)
Source line: 3
	addq	$64, %rdx
	addq	$64, %rsi
	addq	$64, %rdi
	addq	$-8, %rbx
	jne	L240
	cmpq	%r8, %rcx
	je	L350
L290:
	addq	$1, %rcx
	subq	%r15, %rcx
	leaq	-8(%r11,%r15,8), %rdx
	leaq	-8(%r10,%r15,8), %rsi
	leaq	-8(%r9,%r15,8), %rdi
	nopl	(%rax,%rax)
Source line: 4
L320:
	vmovsd	(%rdi), %xmm0           ## xmm0 = mem[0],zero
	vaddsd	(%rsi), %xmm0, %xmm0
	vmovsd	%xmm0, (%rdx)
Source line: 3
	addq	$8, %rdx
	addq	$8, %rsi
	addq	$8, %rdi
	addq	$-1, %rcx
	jne	L320
Source line: 4
L350:
	movq	%rax, -32(%rbp)
Source line: 6
L354:
	movq	-48(%rbp), %rcx
	movq	%rcx, (%r14)
	addq	$40, %rsp
	popq	%rbx
	popq	%r14
	popq	%r15
	popq	%rbp
	vzeroupper
	retq
	nopw	(%rax,%rax)

ネイティブコードはCPUで直接実行されるので、仮想マシン(VM)を使う他のインタプリター方式の言語より高速

Juliaの基礎知識

もうちょっと包括的な日本語のチュートリアル➥ https://github.com/bicycle1885/Julia-Tutorial

基本構文 - 分岐

In [32]:
x = rand([-1, 0, 1])

if x > 0
    println("positive!")
elseif x == 0
    println("zero!")
else
    println("negative!")
end
zero!

基本構文 - 反復

In [33]:
for i in 1:10
    println(i)
end
1
2
3
4
5
6
7
8
9
10
In [34]:
while true
    r = rand()
    @show r
    if r < 0.05
        break
    end
end
r = 0.824963896731119
r = 0.3864837619109138
r = 0.19716180707125286
r = 0.4707863823067948
r = 0.9122340342619175
r = 0.27714398262365547
r = 0.40275437809947934
r = 0.12027556668360484
r = 0.09447130478733246
r = 0.24197683192547736
r = 0.8619030597650075
r = 0.5945882942644753
r = 0.49438472096360275
r = 0.49150286529392995
r = 0.39888971732793155
r = 0.04482709506506044

基本構文 - 関数定義

In [35]:
# functioを使う関数定義
function hello(name)
    return "hello, $(name)!"
end
Out[35]:
hello (generic function with 1 method)
In [36]:
# 1行関数定義
hello(name) = "hello, $(name)!!"
Out[36]:
hello (generic function with 1 method)
In [37]:
# 引数の型制約付き
function hello(name::AbstractString)
    return "hello, $(name)!!!"
end
Out[37]:
hello (generic function with 2 methods)
In [38]:
# 引数と返り値の型制約付き
function hello(name::AbstractString)::String
    return "hello, $(name)!!!!"
end
Out[38]:
hello (generic function with 2 methods)

Juliaの型

In [39]:
# nothingの型
Void
Out[39]:
Void
In [40]:
typeof(nothing)
Out[40]:
Void
In [41]:
# 真偽型
Bool
Out[41]:
Bool
In [42]:
typeof(true)
Out[42]:
Bool
In [43]:
# 符号付き整数
Int8, Int16, Int32, Int64, Int128
Out[43]:
(Int8, Int16, Int32, Int64, Int128)
In [44]:
typeof(1)
Out[44]:
Int64
In [45]:
# 符号無し整数
UInt8, UInt16, UInt32, UInt64, UInt128
Out[45]:
(UInt8, UInt16, UInt32, UInt64, UInt128)
In [46]:
typeof(0x0000000000000001)
Out[46]:
UInt64
In [47]:
# エイリアス
Int == Int64
Out[47]:
true
In [48]:
# エイリアス
UInt == UInt64
Out[48]:
true
In [49]:
# 浮動小数点数
Float16, Float32, Float64
Out[49]:
(Float16, Float32, Float64)
In [50]:
typeof(1.0)
Out[50]:
Float64
In [51]:
# 文字
Char
Out[51]:
Char
In [52]:
typeof('a')
Out[52]:
Char

リテラルによる型の違い

In [53]:
typeof(1)
Out[53]:
Int64
In [54]:
typeof(0x01)
Out[54]:
UInt8
In [55]:
typeof(0x0001)
Out[55]:
UInt16
In [56]:
typeof(1.0)
Out[56]:
Float64

文字列

In [57]:
String
Out[57]:
String
In [58]:
"abracadabra"
Out[58]:
"abracadabra"
In [59]:
"アブラカダブラ"
Out[59]:
"アブラカダブラ"

配列

In [60]:
Array
Out[60]:
Array
In [61]:
# 1次元配列 (ベクトルとも言う)
[1, 2, 3]
Out[61]:
3-element Array{Int64,1}:
 1
 2
 3
In [62]:
# 2次元配列 (行列とも言う)
[1 2 3
 4 5 6]
Out[62]:
2×3 Array{Int64,2}:
 1  2  3
 4  5  6

JuliaにPythonでいうlist型はないが、1次元配列がその代わり

集合

In [63]:
Set
Out[63]:
Set
In [64]:
Set([1, 2, 3])
Out[64]:
Set([2, 3, 1])

辞書

In [65]:
Dict
Out[65]:
Dict
In [66]:
Dict("one" => 1, "two" => 2)
Out[66]:
Dict{String,Int64} with 2 entries:
  "two" => 2
  "one" => 1

Nullable

In [67]:
Nullable
Out[67]:
Nullable
In [68]:
Nullable(1)
Out[68]:
Nullable{Int64}(1)
In [69]:
Nullable{Int64}()
Out[69]:
Nullable{Int64}()

抽象型

Int64など具体型をまとめる型

In [70]:
Int
Out[70]:
Int64
In [71]:
supertype(Int)
Out[71]:
Signed
In [72]:
supertype(supertype(Int))
Out[72]:
Integer
In [73]:
supertype(supertype(supertype(Int)))
Out[73]:
Real
In [74]:
supertype(supertype(supertype(supertype(Int))))
Out[74]:
Number
In [75]:
supertype(supertype(supertype(supertype(supertype(Int)))))
Out[75]:
Any
In [76]:
supertype(supertype(supertype(supertype(supertype(supertype(Int))))))
Out[76]:
Any

抽象型AbstractMatrixのサブタイプ

In [77]:
subtypes(AbstractMatrix)
Out[77]:
21-element Array{Union{DataType, UnionAll},1}:
 AbstractSparseArray{Tv,Ti,2} where Ti where Tv                                                                                                               
 Base.LinAlg.AbstractTriangular                                                                                                                               
 Base.LinAlg.HessenbergQ                                                                                                                                      
 Base.LinAlg.LQPackedQ                                                                                                                                        
 Base.LinAlg.QRCompactWYQ                                                                                                                                     
 Base.LinAlg.QRPackedQ                                                                                                                                        
 Base.LinAlg.SVDOperator                                                                                                                                      
 Base.ReshapedArray{T,2,P,MI} where MI<:Tuple{Vararg{Base.MultiplicativeInverses.SignedMultiplicativeInverse{Int64},N} where N} where P<:AbstractArray where T
 Base.SparseArrays.CHOLMOD.FactorComponent                                                                                                                    
 Bidiagonal                                                                                                                                                   
 ConjArray{T,2,A} where A<:AbstractArray where T                                                                                                              
 DenseArray{T,2} where T                                                                                                                                      
 Diagonal                                                                                                                                                     
 Hermitian                                                                                                                                                    
 PermutedDimsArray{T,2,perm,iperm,AA} where AA<:AbstractArray where iperm where perm where T                                                                  
 PyCall.PyArray{T,2} where T                                                                                                                                  
 RowVector                                                                                                                                                    
 SubArray{T,2,P,I,L} where L where I where P where T                                                                                                          
 SymTridiagonal                                                                                                                                               
 Symmetric                                                                                                                                                    
 Tridiagonal                                                                                                                                                  

パラメトリック型

型パラメーターを取る型

In [78]:
Array
Out[78]:
Array
In [79]:
Array{Int}
Out[79]:
Array{Int64,N} where N
In [80]:
Array{Int,1}
Out[80]:
Array{Int64,1}
In [81]:
Array{Int,2}
Out[81]:
Array{Int64,2}
In [82]:
typeof([1,2,3])
Out[82]:
Array{Int64,1}
In [83]:
typeof([1 2 3; 4 5 6])
Out[83]:
Array{Int64,2}
In [84]:
d = Dict("one" => 1, "two" => 2)
Out[84]:
Dict{String,Int64} with 2 entries:
  "two" => 2
  "one" => 1
In [85]:
typeof(d)
Out[85]:
Dict{String,Int64}

型定義

In [86]:
struct User
    name::String
    registerdate::Date
end
In [87]:
jeff = User("Jeff Bezanson", Date(2010, 3, 4))
Out[87]:
User("Jeff Bezanson", 2010-03-04)
In [88]:
jeff.name
Out[88]:
"Jeff Bezanson"
In [89]:
jeff.registerdate
Out[89]:
2010-03-04
In [90]:
jeff.name = "Stefan Karpinski"
type User is immutable

Stacktrace:
 [1] include_string(::String, ::String) at ./loading.jl:515
In [91]:
mutable struct MutableUser
    name::String
    registerdate::Date
end
In [92]:
viral = MutableUser("Viral Shah", Date(2013, 10, 2))
Out[92]:
MutableUser("Viral Shah", 2013-10-02)
In [93]:
viral.name
Out[93]:
"Viral Shah"
In [94]:
viral.name = "Alan Edelman"
Out[94]:
"Alan Edelman"
In [95]:
viral
Out[95]:
MutableUser("Alan Edelman", 2013-10-02)

多重ディスパッチ

関数+には180を超えるメソッドが定義されている

In [96]:
+
Out[96]:
+ (generic function with 185 methods)
In [97]:
methods(+)
Out[97]:
185 methods for generic function +:
In [98]:
methodswith(Int, +)
Out[98]:
5-element Array{Method,1}:
  • +(x::BigInt, c::Union{Int16, Int32, Int64, Int8}) at gmp.jl:362
  • +(x::BigFloat, c::Union{Int16, Int32, Int64, Int8}) at mpfr.jl:292
  • +{T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8}}(x::T, y::T) at int.jl:32
  • +(c::Union{Int16, Int32, Int64, Int8}, x::BigInt) at gmp.jl:363
  • +(c::Union{Int16, Int32, Int64, Int8}, x::BigFloat) at mpfr.jl:296
In [99]:
1 + 1
Out[99]:
2
In [100]:
struct Point{T}
    x::T
    y::T
end
In [101]:
Base.:(+)(p::Point, q::Point) = Point(p.x + q.x, p.y + q.y)
In [102]:
+
Out[102]:
+ (generic function with 186 methods)
In [103]:
p = Point(1, 2)
q = Point(-2, 4)
Out[103]:
Point{Int64}(-2, 4)
In [104]:
p + q
Out[104]:
Point{Int64}(-1, 6)
In [105]:
p + 1
MethodError: no method matching +(::Point{Int64}, ::Int64)
Closest candidates are:
  +(::Any, ::Any, ::Any, ::Any...) at operators.jl:424
  +(::Complex{Bool}, ::Real) at complex.jl:247
  +(::Char, ::Integer) at char.jl:40
  ...

Stacktrace:
 [1] include_string(::String, ::String) at ./loading.jl:515
In [106]:
Base.:(+)(p::Point, d::Real) = Point(p.x + d, p.y + d)
In [107]:
p + 1
Out[107]:
Point{Int64}(2, 3)
In [108]:
Base.:(*)(p::Point, s::Real) = Point(p.x * s, p.y * s)
In [109]:
p * 3
Out[109]:
Point{Int64}(3, 6)
In [110]:
(p + 1) * 3 == p * 3 + 3
Out[110]:
true
In [111]:
function distance(p::Point)
    return sqrt(p.x^2 + p.y^2)
end
Out[111]:
distance (generic function with 1 method)
In [112]:
distance(p)
Out[112]:
2.23606797749979
In [113]:
function Base.:(+)(p::Point, n::Integer)
    println("You called Point + Integer!")
    return Point(p.x + n, p.y + n)
end
In [114]:
p + 1
You called Point + Integer!
Out[114]:
Point{Int64}(2, 3)
In [115]:
p + 1.0
Out[115]:
Point{Float64}(2.0, 3.0)
In [116]:
function move_right(p::Point)
    return p + Point(1, 0)
end
Out[116]:
move_right (generic function with 1 method)
In [117]:
p
Out[117]:
Point{Int64}(1, 2)
In [118]:
move_right(p)
Out[118]:
Point{Int64}(2, 2)

多重ディスパッチのポイント

  • すべての引数の扱いは平等 (右も左もない)
  • メソッドのうち、型が一番良く合うものが実際に呼ばれる

C言語との連携

ccallでC言語の関数を呼び出せる

In [119]:
# https://www.gnu.org/software/libc/manual/html_node/Trig-Functions.html
ccall((:sin, "libc"), Cdouble, (Cdouble,), π/2)
Out[119]:
1.0
In [120]:
# https://www.gnu.org/software/libc/manual/html_node/Environment-Access.html
unsafe_string(ccall((:getenv, "libc"), Cstring, (Cstring,), "SHELL"))
Out[120]:
"/usr/local/bin/fish"

JuliaとCの構造体はメモリレイアウトの互換性があるので、互いに読み書きできる。具体例は以下のパッケージを参照

ブロードキャスト

スカラーや配列を同じ形に揃えて、要素毎の演算をすること。

In [121]:
x = [1,2,3]; y = [4,5,6]
Out[121]:
3-element Array{Int64,1}:
 4
 5
 6
In [122]:
# スカラー倍 (ブロードキャストとは言わない)
x * 3
Out[122]:
3-element Array{Int64,1}:
 3
 6
 9
In [123]:
# ベクトル和 (ブロードキャストとは言わない)
x + y
Out[123]:
3-element Array{Int64,1}:
 5
 7
 9
In [124]:
# 要素毎の積 (ブロードキャスト)
x .* 3
Out[124]:
3-element Array{Int64,1}:
 3
 6
 9
In [125]:
# 要素毎の和 (ブロードキャスト)
x .+ y
Out[125]:
3-element Array{Int64,1}:
 5
 7
 9

列ベクトルと列ベクトルの積は計算できない

In [126]:
x * y
DimensionMismatch("Cannot multiply two vectors")

Stacktrace:
 [1] *(::Array{Int64,1}, ::Array{Int64,1}) at ./linalg/rowvector.jl:184
 [2] include_string(::String, ::String) at ./loading.jl:515

ブロードキャストを使えば要素毎の積なら計算できる

In [127]:
x .* y
Out[127]:
3-element Array{Int64,1}:
  4
 10
 18

列ベクトルと行ベクトルの和は計算できない

In [128]:
x + y'
DimensionMismatch("dimensions must match")

Stacktrace:
 [1] promote_shape(::Tuple{Base.OneTo{Int64},Base.OneTo{Int64}}, ::Tuple{Base.OneTo{Int64}}) at ./indices.jl:79
 [2] promote_shape(::Tuple{Base.OneTo{Int64}}, ::Tuple{Base.OneTo{Int64},Base.OneTo{Int64}}) at ./indices.jl:75
 [3] +(::Array{Int64,1}, ::RowVector{Int64,Array{Int64,1}}) at ./arraymath.jl:37
 [4] include_string(::String, ::String) at ./loading.jl:515

ブロードキャストはできる

In [129]:
x .+ y'
Out[129]:
3×3 Array{Int64,2}:
 5  6  7
 6  7  8
 7  8  9
In [130]:
A = [
    1 2
    3 4
    5 6
]
Out[130]:
3×2 Array{Int64,2}:
 1  2
 3  4
 5  6

行列と列ベクトルの和は計算できない

In [131]:
A + x
DimensionMismatch("dimensions must match")

Stacktrace:
 [1] promote_shape(::Tuple{Base.OneTo{Int64},Base.OneTo{Int64}}, ::Tuple{Base.OneTo{Int64}}) at ./indices.jl:84
 [2] +(::Array{Int64,2}, ::Array{Int64,1}) at ./arraymath.jl:37
 [3] include_string(::String, ::String) at ./loading.jl:515

ブロードキャストはできる

In [132]:
A .+ x
Out[132]:
3×2 Array{Int64,2}:
 2  3
 5  6
 8  9

マクロ

  • 関数では実現できない、コードの操作ができる
  • Cのマクロとは異なるLispから受け継いだマクロ
In [133]:
x = -3
Out[133]:
-3
In [134]:
@show x;
x = -3
In [135]:
@assert x > 0
AssertionError: x > 0

Stacktrace:
 [1] include_string(::String, ::String) at ./loading.jl:515

標準ライブラリで提供されているマクロは何種類かに分けられる。

  1. 開発支援系: @show, @less, @which, @code_warntype, etc.
  2. 特殊構文系: @assert, @goto, @label, @async, @parallel, etc.
  3. コンパイラヒント系: @inbounds, @inline, @fastmath, etc.
  4. メタデータ系: @__DIR__, @__FILE__, etc.
  5. 非標準文字列系: @r_str, @ip_str, @v_str, etc.
In [136]:
ismatch(r"0x[0-9A-Fa-f]{2}", "0x3F")
Out[136]:
true

Juliaの実行モデル

  • Juliaの実行モデルを理解することは速いコードを書く上で非常に重要
  • Juliaはちょっと特殊な実行の仕方をするので、最初は少し戸惑う

Juliaは本当に速い?

答: 速い

現時点でC/C++より遅くなりそうなケース

  • マルチスレッドプログラミング (理由: Juliaのマルチスレッドサポートが弱い)
  • 細かいSIMDを使ったプログラミング (理由: x86のSIMD intrinsicsがない)

これ以外のケースでは、バグでないJuliaの制約でパフォーマンスが出なかったことは体験してない(個人の感想です)。

希望の光

具体例: Base64のデコーダー

64+1種類のASCII文字を使ってエンコードされたバイナリデータを元のデータに復元する。

ポイント

  • 4バイトのASCII文字列にエンコードされたデータを3バイトのバイナリに変換
  • 特定の文字の無視・パディングの処理などがあり、それなりに複雑
  • 表引きによるアルゴリズム
function TranscodingStreams.process(
        codec  :: Base64Decoder,
        input  :: Memory,
        output :: Memory,
        error  :: Error)
    table = codec.table
    state = codec.state
    buffer = codec.buffer

    # Check if we can encode data.
    if !is_running(state)
        error[] = ArgumentError("decoding is already finished")
        return 0, 0, :error
    elseif output.size < 3
        # Need more output space.
        return 0, 0, :ok
    end

    # Load the frist bytes.
    i = j = 0
    while buffer.size < 3 && i < input.size
        buffer[buffer.size+=1] = input[i+=1]
    end
    c1 = c2 = c3 = c4 = BASE64_CODEIGN
    if buffer.size  1
        c1 = decode(table, buffer[1])
    end
    if buffer.size  2
        c2 = decode(table, buffer[2])
    end
    if buffer.size  3
        c3 = decode(table, buffer[3])
    end
    empty!(buffer)

    # Start decoding loop.
    status = :ok
    @inbounds while true
        if c1 > 0x3f || c2 > 0x3f || c3 > 0x3f || c4 > 0x3f
            i, j, status = decode_irregular(table, c1, c2, c3, c4, input, i, output, j, error)
        else
            output[j+1] = c1 << 2 | c2 >> 4
            output[j+2] = c2 << 4 | c3 >> 2
            output[j+3] = c3 << 6 | c4
            j += 3
        end
        if i + 4  input.size && j + 3  output.size && status == :ok
            c1 = decode(table, input[i+1])
            c2 = decode(table, input[i+2])
            c3 = decode(table, input[i+3])
            c4 = decode(table, input[i+4])
            i += 4
        else
            break
        end
    end

    # Epilogue.
    if status == :end || status == :error
        finish!(state)
    end
    return i, j, status
end

# Decode irregular code (e.g. non-alphabet, padding, etc.).
function decode_irregular(table, c1, c2, c3, c4, input, i, output, j, error)
    # Skip ignored chars.
    while true
        if c1 == BASE64_CODEIGN
            c1, c2, c3 = c2, c3, c4
        elseif c2 == BASE64_CODEIGN
            c2, c3 = c3, c4
        elseif c3 == BASE64_CODEIGN
            c3 = c4
        elseif c4 == BASE64_CODEIGN
            # pass
        else
            break
        end
        if i + 1  input.size
            c4 = decode(table, input[i+=1])
        else
            c4 = BASE64_CODEEND
            break
        end
    end

    # Write output.
    if c1  0x3f && c2  0x3f && c3  0x3f && c4  0x3f
        output[j+=1] = c1 << 2 | c2 >> 4
        output[j+=1] = c2 << 4 | c3 >> 2
        output[j+=1] = c3 << 6 | c4
        status = :ok
    elseif c1  0x3f && c2  0x3f && c3  0x3f && c4 == BASE64_CODEPAD
        c4 = 0x00
        output[j+=1] = c1 << 2 | c2 >> 4
        output[j+=1] = c2 << 4 | c3 >> 2
        status = :end
    elseif c1  0x3f && c2  0x3f && c3 == c4 == BASE64_CODEPAD
        c3 = c4 = 0x00
        output[j+=1] = c1 << 2 | c2 >> 4
        status = :end
    elseif c1 == c2 == c3 == BASE64_CODEIGN && c4 == BASE64_CODEEND
        status = :end
    else
        error[] = ArgumentError("invalid data")
        status = :error
    end
    return i, j, status
end

https://github.com/bicycle1885/CodecBase.jl/blob/master/src/base64/decoder.jl#L50-L159

ベンチマーク

Pythonの標準ライブラリのC言語による実装より高速

Julia 0.6.0:

julia> sizeof(data)
286180

julia> @benchmark transcode(Base64Decoder(), data)
BenchmarkTools.Trial:
  memory estimate:  490.31 KiB
  allocs estimate:  19
  --------------
  minimum time:     372.894 μs (0.00% GC)
  median time:      561.580 μs (0.00% GC)
  mean time:        594.615 μs (6.84% GC)
  maximum time:     4.291 ms (68.99% GC)
  --------------
  samples:          8379
  evals/sample:     1

Python 3.5.2:

In [13]: len(data)
Out[13]: 286180

In [14]: %timeit base64.decodebytes(data)
1.16 ms ± 14.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

実行するまでの流れ

ユーザーがREPLに打ち込んだ処理が実行されるまでの流れ

  1. コンパイラーがJuliaのソースコードをパースする
  2. マクロ・構文糖衣の展開などを行う
  3. 低レベルなJuliaの中間表現に落とし込む
  4. 型推論を行う
  5. LLVMの中間表現に落とし込む
  6. ネイティブコードに落とし込む
  7. 実行する
In [137]:
# ソースコードのパース
parse("1 + 1")
Out[137]:
:(1 + 1)
In [138]:
parse("""
function foobar(x)
    @assert x > 0
end
""")
Out[138]:
:(function foobar(x) # none, line 2:
        @assert x > 0
    end)

parseExprのオブジェクトを返す。Exprは抽象構文木(AST)。

In [139]:
expr = parse("1 + 2 * 3")
Out[139]:
:(1 + 2 * 3)
In [140]:
dump(expr)
Expr
  head: Symbol call
  args: Array{Any}((3,))
    1: Symbol +
    2: Int64 1
    3: Expr
      head: Symbol call
      args: Array{Any}((3,))
        1: Symbol *
        2: Int64 2
        3: Int64 3
      typ: Any
  typ: Any
In [141]:
# ifelseなどは複数のif-elseに展開される
quote
    if x < 0
        println("x is negative")
    elseif x == 0
        println("x is zero")
    else
        println("x is positive")
    end
end
Out[141]:
quote  # In[141], line 3:
    if x < 0 # In[141], line 4:
        println("x is negative")
    else  # In[141], line 5:
        if x == 0 # In[141], line 6:
            println("x is zero")
        else  # In[141], line 8:
            println("x is positive")
        end
    end
end
In [142]:
# マクロの展開する関数
macroexpand(expr)
Out[142]:
:(1 + 2 * 3)
In [143]:
# @から始まるマクロはこの関数で展開される
macroexpand(:(@assert x > 0))
Out[143]:
:(if x > 0
        nothing
    else 
        (Base.throw)(Base.Main.Base.AssertionError("x > 0"))
    end)
In [144]:
macroexpand(quote
    function foobar(x)
        @assert x > 0
    end
end)
Out[144]:
quote  # In[144], line 2:
    function foobar(x) # In[144], line 3:
        if x > 0
            nothing
        else 
            (Base.throw)(Base.Main.Base.AssertionError("x > 0"))
        end
    end
end
In [145]:
function foobar(x)
    @assert x > 0
end
Out[145]:
foobar (generic function with 1 method)
In [146]:
# Juliaの中間表現はunlessの分岐とgotoばかりのフラットなコード
code_lowered(foobar, (Int,))
Out[146]:
1-element Array{CodeInfo,1}:
 CodeInfo(:(begin 
        nothing
        unless x > 0 goto 4
        return
        4: 
        return (Base.throw)(((Core.getfield)((Core.getfield)(Base.Main, :Base), :AssertionError))("x > 0"))
    end))
In [147]:
function foobar(n)
    x = 0
    for i in 1:n
        x += i
    end
    return x
end
Out[147]:
foobar (generic function with 1 method)
In [148]:
# forループもunlessとgotoへ展開
code_lowered(foobar, (Int,))
Out[148]:
1-element Array{CodeInfo,1}:
 CodeInfo(:(begin 
        nothing
        x = 0 # line 3:
        SSAValue(0) = (Main.colon)(1, n)
        #temp# = (Base.start)(SSAValue(0))
        6: 
        unless !((Base.done)(SSAValue(0), #temp#)) goto 15
        SSAValue(1) = (Base.next)(SSAValue(0), #temp#)
        i = (Core.getfield)(SSAValue(1), 1)
        #temp# = (Core.getfield)(SSAValue(1), 2) # line 4:
        x = x + i
        13: 
        goto 6
        15:  # line 6:
        return x
    end))
In [149]:
# Juliaの中間表現に型推論で型をつける
code_typed(foobar, (Int,))
Out[149]:
1-element Array{Any,1}:
 CodeInfo(:(begin 
        x = 0 # line 3:
        SSAValue(2) = (Base.select_value)((Base.sle_int)(1, n)::Bool, n, (Base.sub_int)(1, 1)::Int64)::Int64
        #temp# = 1
        5: 
        unless (Base.not_int)((#temp# === (Base.add_int)(SSAValue(2), 1)::Int64)::Bool)::Bool goto 15
        SSAValue(3) = #temp#
        SSAValue(4) = (Base.add_int)(#temp#, 1)::Int64
        i = SSAValue(3)
        #temp# = SSAValue(4) # line 4:
        x = (Base.add_int)(x, i)::Int64
        13: 
        goto 5
        15:  # line 6:
        return x
    end))=>Int64
In [150]:
# LLVM中間表現に変換
code_llvm(foobar, (Int,))
define i64 @julia_foobar_63146(i64) #0 !dbg !5 {
top:
  %1 = icmp slt i64 %0, 1
  br i1 %1, label %L15, label %if.lr.ph

if.lr.ph:                                         ; preds = %top
  %2 = shl i64 %0, 1
  %3 = add i64 %0, -1
  %4 = zext i64 %3 to i65
  %5 = add i64 %0, -2
  %6 = zext i64 %5 to i65
  %7 = mul i65 %4, %6
  %8 = lshr i65 %7, 1
  %9 = trunc i65 %8 to i64
  %10 = add i64 %2, %9
  %11 = add i64 %10, -1
  br label %L15

L15:                                              ; preds = %if.lr.ph, %top
  %x.0.lcssa = phi i64 [ %11, %if.lr.ph ], [ 0, %top ]
  ret i64 %x.0.lcssa
}
In [151]:
# nativeコードに変換
code_native(foobar, (Int,))
	.section	__TEXT,__text,regular,pure_instructions
Filename: In[147]
	pushq	%rbp
	movq	%rsp, %rbp
	xorl	%eax, %eax
Source line: 3
	testq	%rdi, %rdi
	jle	L34
	leaq	-1(%rdi), %rdx
	leaq	-2(%rdi), %rax
	mulxq	%rax, %rax, %rcx
	shldq	$63, %rax, %rcx
	leaq	-1(%rcx,%rdi,2), %rax
Source line: 6
L34:
	popq	%rbp
	retq
	nopw	%cs:(%rax,%rax)

REPLにコードを打ち込むと上の処理を行う

In [152]:
foobar(10)
Out[152]:
55

特殊化 (specialization)

In [153]:
function foobar(n)
    return n * 2
end
Out[153]:
foobar (generic function with 1 method)
In [154]:
@code_typed foobar(1)
Out[154]:
CodeInfo(:(begin 
        return (Base.mul_int)(n, 2)::Int64
    end))=>Int64
In [155]:
@code_typed foobar(1.0)
Out[155]:
CodeInfo(:(begin 
        return (Base.mul_float)(n, (Base.sitofp)(Float64, 2)::Float64)::Float64
    end))=>Float64
  • 引数の型が異なれば、型推論の結果も違う
  • 関数は引数の型の組み合わせだけ特殊化(specialize)される
In [156]:
function foobar(n)
    if n isa Integer
        println("Integer!")
    end
    return n * 2
end
Out[156]:
foobar (generic function with 1 method)
In [157]:
foobar(1)
Integer!
Out[157]:
2
In [158]:
foobar(1.0)
Out[158]:
2.0
In [159]:
@code_typed foobar(1)
Out[159]:
CodeInfo(:(begin  # line 3:
        $(Expr(:inbounds, false))
        # meta: location coreio.jl println 5
        SSAValue(0) = (Core.typeassert)(Base.STDOUT, Base.IO)::IO
        # meta: pop location
        $(Expr(:inbounds, :pop))
        (Base.print)(SSAValue(0), "Integer!", $(QuoteNode('\n')))::Void
        8:  # line 5:
        return (Base.mul_int)(n, 2)::Int64
    end))=>Int64

特殊化によりprintlnが分岐ごと消えている!

In [160]:
@code_typed foobar(1.0)
Out[160]:
CodeInfo(:(begin 
        goto 3 # line 3:
        3:  # line 5:
        return (Base.mul_float)(n, (Base.sitofp)(Float64, 2)::Float64)::Float64
    end))=>Float64

トップレベル

In [161]:
start_ns = time_ns()
s = 0.0
for _ in 1:1_000_000
    s += rand()
end
@show Int(time_ns() - start_ns)
Int(time_ns() - start_ns) = 87514105
Out[161]:
87514105
In [162]:
function loop()
    s = 0.0
    for _ in 1:1_000_000
        s += rand()
    end
    return s
end
Out[162]:
loop (generic function with 1 method)
In [163]:
start_ns = time_ns()
s = loop()
@show Int(time_ns() - start_ns)
Int(time_ns() - start_ns) = 14248780
Out[163]:
14248780
  • 同じ計算なのに上のコードは20倍くらい遅い!
  • 上のコードはトップレベルで実行されているのに対し、下のコードは関数内のループ
  • トップレベルの処理はJITコンパイルされない!

使い分け:

  • トップレベルでは、関数の定義や関数呼び出しを行う
  • 関数内では、重い処理を行う

型の安定性

  • Juliaの高速化は、曖昧性の少ないコードを書くこと
  • 型が決定できないとき、Juliaはすごく遅くなる!
In [164]:
function collatz1(n)
    step = 0
    while n != 1
        step += 1
        if mod(n, 2) == 0
            n /= 2
        else
            n = n * 3 + 1
        end
    end
    return step
end
Out[164]:
collatz1 (generic function with 1 method)
In [165]:
collatz1(10)
Out[165]:
6
In [166]:
using BenchmarkTools
In [167]:
@benchmark for n in 1:10000; collatz1(n); end
Out[167]:
BenchmarkTools.Trial: 
  memory estimate:  86.06 MiB
  allocs estimate:  5640009
  --------------
  minimum time:     110.469 ms (5.88% GC)
  median time:      116.169 ms (5.97% GC)
  mean time:        117.584 ms (5.84% GC)
  maximum time:     132.606 ms (5.19% GC)
  --------------
  samples:          43
  evals/sample:     1
In [168]:
@code_typed collatz1(10)
Out[168]:
CodeInfo(:(begin 
        [email protected]_4 = [email protected]_2
        step = 0 # line 3:
        4: 
        unless ([email protected]_4 isa Float64)::Bool goto 8
        #temp#@_5 = MethodInstance for !=(::Float64, ::Int64)
        goto 17
        8: 
        unless ([email protected]_4 isa Int64)::Bool goto 12
        #temp#@_5 = MethodInstance for !=(::Int64, ::Int64)
        goto 17
        12: 
        goto 14
        14: 
        #temp#@_6 = ([email protected]_4 != 1)::Bool
        goto 19
        17: 
        #temp#@_6 = $(Expr(:invoke, :(#temp#@_5), :(Main.!=), :([email protected]_4), 1))
        19: 
        unless #temp#@_6 goto 108 # line 4:
        step = (Base.add_int)(step, 1)::Int64 # line 5:
        unless ([email protected]_4 isa Float64)::Bool goto 27
        #temp#@_7 = MethodInstance for mod(::Float64, ::Int64)
        goto 36
        27: 
        unless ([email protected]_4 isa Int64)::Bool goto 31
        #temp#@_7 = MethodInstance for mod(::Int64, ::Int64)
        goto 36
        31: 
        goto 33
        33: 
        #temp#@_8 = (Main.mod)([email protected]_4, 2)::Union{Float64, Int64}
        goto 38
        36: 
        #temp#@_8 = $(Expr(:invoke, :(#temp#@_7), :(Main.mod), :([email protected]_4), 2))
        38: 
        unless (#temp#@_8 isa Float64)::Bool goto 42
        #temp#@_9 = MethodInstance for ==(::Float64, ::Int64)
        goto 51
        42: 
        unless (#temp#@_8 isa Int64)::Bool goto 46
        #temp#@_9 = MethodInstance for ==(::Int64, ::Int64)
        goto 51
        46: 
        goto 48
        48: 
        #temp#@_10 = (#temp#@_8 == 0)::Bool
        goto 53
        51: 
        #temp#@_10 = $(Expr(:invoke, :(#temp#@_9), :(Main.==), :(#temp#@_8), 0))
        53: 
        unless #temp#@_10 goto 73 # line 6:
        unless ([email protected]_4 isa Float64)::Bool goto 59
        #temp#@_11 = MethodInstance for /(::Float64, ::Int64)
        goto 68
        59: 
        unless ([email protected]_4 isa Int64)::Bool goto 63
        #temp#@_11 = MethodInstance for /(::Int64, ::Int64)
        goto 68
        63: 
        goto 65
        65: 
        #temp#@_12 = ([email protected]_4 / 2)::Float64
        goto 70
        68: 
        #temp#@_12 = $(Expr(:invoke, :(#temp#@_11), :(Main./), :([email protected]_4), 2))
        70: 
        [email protected]_4 = #temp#@_12
        goto 106
        73:  # line 8:
        unless ([email protected]_4 isa Float64)::Bool goto 78
        #temp#@_13 = MethodInstance for *(::Float64, ::Int64)
        goto 87
        78: 
        unless ([email protected]_4 isa Int64)::Bool goto 82
        #temp#@_13 = MethodInstance for *(::Int64, ::Int64)
        goto 87
        82: 
        goto 84
        84: 
        #temp#@_14 = ([email protected]_4 * 3)::Union{Float64, Int64}
        goto 89
        87: 
        #temp#@_14 = $(Expr(:invoke, :(#temp#@_13), :(Main.*), :([email protected]_4), 3))
        89: 
        unless (#temp#@_14 isa Float64)::Bool goto 93
        #temp#@_15 = MethodInstance for +(::Float64, ::Int64)
        goto 102
        93: 
        unless (#temp#@_14 isa Int64)::Bool goto 97
        #temp#@_15 = MethodInstance for +(::Int64, ::Int64)
        goto 102
        97: 
        goto 99
        99: 
        #temp#@_16 = (#temp#@_14 + 1)::Union{Float64, Int64}
        goto 104
        102: 
        #temp#@_16 = $(Expr(:invoke, :(#temp#@_15), :(Main.+), :(#temp#@_14), 1))
        104: 
        [email protected]_4 = #temp#@_16
        106: 
        goto 4
        108:  # line 11:
        return step
    end))=>Int64
In [169]:
function collatz2(n)
    step = 0
    while n != 1
        step += 1
        if mod(n, 2) == 0
            n = div(n, 2)
        else
            n = n * 3 + 1
        end
    end
    return step
end
Out[169]:
collatz2 (generic function with 1 method)
In [170]:
collatz2(10)
Out[170]:
6
In [171]:
@benchmark for n in 1:10000; collatz2(n); end
Out[171]:
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     3.080 ms (0.00% GC)
  median time:      3.298 ms (0.00% GC)
  mean time:        3.386 ms (0.00% GC)
  maximum time:     17.355 ms (0.00% GC)
  --------------
  samples:          1473
  evals/sample:     1
In [172]:
@code_warntype collatz2(10)
Variables:
  #self#::#collatz2
  [email protected]_2::Int64
  step::Int64
  [email protected]_4::Int64
  d::Int64
  #temp#::Int64

Body:
  begin 
      [email protected]_4::Int64 = [email protected]_2::Int64
      step::Int64 = 0 # line 3:
      4: 
      unless (Base.not_int)(([email protected]_4::Int64 === 1)::Bool)::Bool goto 32 # line 4:
      step::Int64 = (Base.add_int)(step::Int64, 1)::Int64 # line 5:
      $(Expr(:inbounds, false))
      # meta: location int.jl mod 170
      unless (2 === -1)::Bool goto 14
      #temp#::Int64 = 0
      goto 20
      14:  # line 171:
      # meta: location int.jl fld 191
      d::Int64 = (Base.checked_sdiv_int)([email protected]_4::Int64, 2)::Int64
      # meta: pop location
      #temp#::Int64 = (Base.sub_int)([email protected]_4::Int64, (Base.mul_int)((Base.sub_int)(d::Int64, (Base.and_int)((Base.zext_int)(Int64, (Base.and_int)((Base.slt_int)((Base.xor_int)([email protected]_4::Int64, 2)::Int64, 0)::Bool, (Base.not_int)(((Base.mul_int)(d::Int64, 2)::Int64 === [email protected]_4::Int64)::Bool)::Bool)::Bool)::Int64, 1)::Int64)::Int64, 2)::Int64)::Int64
      20: 
      # meta: pop location
      $(Expr(:inbounds, :pop))
      unless (#temp#::Int64 === 0)::Bool goto 27 # line 6:
      [email protected]_4::Int64 = (Base.checked_sdiv_int)([email protected]_4::Int64, 2)::Int64
      goto 30
      27:  # line 8:
      [email protected]_4::Int64 = (Base.add_int)((Base.mul_int)([email protected]_4::Int64, 3)::Int64, 1)::Int64
      30: 
      goto 4
      32:  # line 11:
      return step::Int64
  end::Int64

メタプログラミング

  • マクロを使うと、コードをコードで編集できる
  • @show@assertが標準にもあるマクロ
  • もちろん自分でも定義できる
In [173]:
macro myassert(ex)
    msg = "$(sprint(print, ex)) is not satisfied"
    quote
        if !$(ex)
            throw(AssertionError($(msg)))
        end
    end
end
Out[173]:
@myassert (macro with 1 method)
In [174]:
x = 1
Out[174]:
1
In [175]:
@myassert x > 0
In [176]:
x = 0
Out[176]:
0
In [177]:
@myassert x > 0
AssertionError: x > 0 is not satisfied

Stacktrace:
 [1] include_string(::String, ::String) at ./loading.jl:515

具体例: 正規表現をJuliaへコンパイル

Automa.jlを使って、実行時のコード生成でどういうことができるかをデモンストレーションしてみる。

In [178]:
import Automa
import Automa.RegExp: @re_str
const re = Automa.RegExp

oct      = re"0o[0-7]+"
dec      = re"[-+]?[0-9]+"
hex      = re"0x[0-9A-Fa-f]+"
prefloat = re"[-+]?([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)"
float    = prefloat | re.cat(prefloat | re"[-+]?[0-9]+", re"[eE][-+]?[0-9]+")
number   = oct | dec | hex | float
numbers  = re.cat(re.opt(number), re.rep(re" +" * number), re" *");
In [179]:
number.actions[:enter] = [:mark]
oct.actions[:exit]     = [:oct]
dec.actions[:exit]     = [:dec]
hex.actions[:exit]     = [:hex]
float.actions[:exit]   = [:float]
Out[179]:
1-element Array{Symbol,1}:
 :float
In [180]:
actions = Dict(
    :mark  => :(mark = p),
    :oct   => :(emit(:oct)),
    :dec   => :(emit(:dec)),
    :hex   => :(emit(:hex)),
    :float => :(emit(:float)),
);
In [181]:
machine = Automa.compile(numbers)
context = Automa.CodeGenContext(generator=:goto, clean=true)
@eval function tokenize(data::String)
    tokens = Tuple{Symbol,String}[]
    mark = 0
    $(Automa.generate_init_code(context, machine))
    p_end = p_eof = endof(data)
    emit(kind) = push!(tokens, (kind, data[mark:p-1]))
    $(Automa.generate_exec_code(context, machine, actions))
    return tokens, cs == 0 ? :ok : cs < 0 ? :error : :incomplete
end
Out[181]:
tokenize (generic function with 1 method)
In [182]:
tokens, status = tokenize("1 0x0123BEEF 0o754 3.14 -1e4 +6.022045e23")
tokens
Out[182]:
6-element Array{Tuple{Symbol,String},1}:
 (:dec, "1")             
 (:hex, "0x0123BEEF")    
 (:oct, "0o754")         
 (:float, "3.14")        
 (:float, "-1e4")        
 (:float, "+6.022045e23")
In [183]:
write("numbers.dot", Automa.machine2dot(machine))
run(`dot -Tpng -o numbers.png numbers.dot`)
html"<img src=\"numbers.png\">"
Out[183]:
In [184]:
tokenize("0x")
Out[184]:
(Tuple{Symbol,String}[], :incomplete)
In [185]:
tokenize("0x1234")
Out[185]:
(Tuple{Symbol,String}[(:hex, "0x1234")], :ok)
In [186]:
tokenize("0x1234G")
Out[186]:
(Tuple{Symbol,String}[], :error)
In [187]:
Automa.generate_exec_code(context, machine, actions)
Out[187]:
quote 
    if p > p_end
        @goto exit
    end
    ##722 = (Automa.SizedMemory)(data)
    if cs == 1
        @goto state_case_1
    else 
        if cs == 2
            @goto state_case_2
        else 
            if cs == 3
                @goto state_case_3
            else 
                if cs == 4
                    @goto state_case_4
                else 
                    if cs == 5
                        @goto state_case_5
                    else 
                        if cs == 6
                            @goto state_case_6
                        else 
                            if cs == 7
                                @goto state_case_7
                            else 
                                if cs == 8
                                    @goto state_case_8
                                else 
                                    if cs == 9
                                        @goto state_case_9
                                    else 
                                        if cs == 10
                                            @goto state_case_10
                                        else 
                                            if cs == 11
                                                @goto state_case_11
                                            else 
                                                if cs == 12
                                                    @goto state_case_12
                                                else 
                                                    if cs == 13
                                                        @goto state_case_13
                                                    else 
                                                        @goto exit
                                                    end
                                                end
                                            end
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end
    end
    @label state_1_action_2
    emit(:float)
    @goto state_1
    @label state_1_action_3
    emit(:dec)
    @goto state_1
    @label state_1_action_4
    emit(:hex)
    @goto state_1
    @label state_1_action_5
    emit(:oct)
    @goto state_1
    @label state_1
    p += 1
    if p > p_end
        cs = 1
        @goto exit
    end
    @label state_case_1
    ##723 = (getindex)(##722, p + 0)
    if (##723 in 0x31:0x39 || false) && true
        @goto state_3_action_1
    else 
        if (##723 in 0x2b:0x2b || (##723 in 0x2d:0x2d || false)) && true
            @goto state_4_action_1
        else 
            if (##723 in 0x20:0x20 || false) && true
                @goto state_1
            else 
                if (##723 in 0x30:0x30 || false) && true
                    @goto state_2_action_1
                else 
                    if (##723 in 0x2e:0x2e || false) && true
                        @goto state_5_action_1
                    else 
                        cs = -1
                        @goto exit
                    end
                end
            end
        end
    end
    @label state_5_action_1
    mark = p
    @goto state_5
    @label state_5
    p += 1
    if p > p_end
        cs = 5
        @goto exit
    end
    @label state_case_5
    ##723 = (getindex)(##722, p + 0)
    if (##723 in 0x30:0x39 || false) && true
        @goto state_6
    else 
        cs = -5
        @goto exit
    end
    @label state_6
    p += 1
    if p > p_end
        cs = 6
        @goto exit
    end
    @label state_case_6
    ##723 = (getindex)(##722, p + 0)
    if (##723 in 0x30:0x39 || false) && true
        @goto state_6
    else 
        if (##723 in 0x45:0x45 || (##723 in 0x65:0x65 || false)) && true
            @goto state_7
        else 
            if (##723 in 0x20:0x20 || false) && true
                @goto state_1_action_2
            else 
                cs = -6
                @goto exit
            end
        end
    end
    @label state_7
    p += 1
    if p > p_end
        cs = 7
        @goto exit
    end
    @label state_case_7
    ##723 = (getindex)(##722, p + 0)
    if (##723 in 0x30:0x39 || false) && true
        @goto state_8
    else 
        if (##723 in 0x2b:0x2b || (##723 in 0x2d:0x2d || false)) && true
            @goto state_9
        else 
            cs = -7
            @goto exit
        end
    end
    @label state_9
    p += 1
    if p > p_end
        cs = 9
        @goto exit
    end
    @label state_case_9
    ##723 = (getindex)(##722, p + 0)
    if (##723 in 0x30:0x39 || false) && true
        @goto state_8
    else 
        cs = -9
        @goto exit
    end
    @label state_8
    p += 1
    if p > p_end
        cs = 8
        @goto exit
    end
    @label state_case_8
    ##723 = (getindex)(##722, p + 0)
    if (##723 in 0x30:0x39 || false) && true
        @goto state_8
    else 
        if (##723 in 0x20:0x20 || false) && true
            @goto state_1_action_2
        else 
            cs = -8
            @goto exit
        end
    end
    @label state_4_action_1
    mark = p
    @goto state_4
    @label state_4
    p += 1
    if p > p_end
        cs = 4
        @goto exit
    end
    @label state_case_4
    ##723 = (getindex)(##722, p + 0)
    if (##723 in 0x30:0x39 || false) && true
        @goto state_3
    else 
        if (##723 in 0x2e:0x2e || false) && true
            @goto state_5
        else 
            cs = -4
            @goto exit
        end
    end
    @label state_3_action_1
    mark = p
    @goto state_3
    @label state_3
    p += 1
    if p > p_end
        cs = 3
        @goto exit
    end
    @label state_case_3
    ##723 = (getindex)(##722, p + 0)
    if (##723 in 0x30:0x39 || false) && true
        @goto state_3
    else 
        if (##723 in 0x45:0x45 || (##723 in 0x65:0x65 || false)) && true
            @goto state_7
        else 
            if (##723 in 0x20:0x20 || false) && true
                @goto state_1_action_3
            else 
                if (##723 in 0x2e:0x2e || false) && true
                    @goto state_6
                else 
                    cs = -3
                    @goto exit
                end
            end
        end
    end
    @label state_2_action_1
    mark = p
    @goto state_2
    @label state_2
    p += 1
    if p > p_end
        cs = 2
        @goto exit
    end
    @label state_case_2
    ##723 = (getindex)(##722, p + 0)
    if (##723 in 0x30:0x39 || false) && true
        @goto state_3
    else 
        if (##723 in 0x45:0x45 || (##723 in 0x65:0x65 || false)) && true
            @goto state_7
        else 
            if (##723 in 0x6f:0x6f || false) && true
                @goto state_10
            else 
                if (##723 in 0x20:0x20 || false) && true
                    @goto state_1_action_3
                else 
                    if (##723 in 0x78:0x78 || false) && true
                        @goto state_11
                    else 
                        if (##723 in 0x2e:0x2e || false) && true
                            @goto state_6
                        else 
                            cs = -2
                            @goto exit
                        end
                    end
                end
            end
        end
    end
    @label state_11
    p += 1
    if p > p_end
        cs = 11
        @goto exit
    end
    @label state_case_11
    ##723 = (getindex)(##722, p + 0)
    if (##723 in 0x30:0x39 || (##723 in 0x41:0x46 || (##723 in 0x61:0x66 || false))) && true
        @goto state_12
    else 
        cs = -11
        @goto exit
    end
    @label state_12
    p += 1
    if p > p_end
        cs = 12
        @goto exit
    end
    @label state_case_12
    ##723 = (getindex)(##722, p + 0)
    if (##723 in 0x30:0x39 || (##723 in 0x41:0x46 || (##723 in 0x61:0x66 || false))) && true
        @goto state_12
    else 
        if (##723 in 0x20:0x20 || false) && true
            @goto state_1_action_4
        else 
            cs = -12
            @goto exit
        end
    end
    @label state_10
    p += 1
    if p > p_end
        cs = 10
        @goto exit
    end
    @label state_case_10
    ##723 = (getindex)(##722, p + 0)
    if (##723 in 0x30:0x37 || false) && true
        @goto state_13
    else 
        cs = -10
        @goto exit
    end
    @label state_13
    p += 1
    if p > p_end
        cs = 13
        @goto exit
    end
    @label state_case_13
    ##723 = (getindex)(##722, p + 0)
    if (##723 in 0x30:0x37 || false) && true
        @goto state_13
    else 
        if (##723 in 0x20:0x20 || false) && true
            @goto state_1_action_5
        else 
            cs = -13
            @goto exit
        end
    end
    @label exit
    if p > p_eof ≥ 0 && cs ∈ Automa.StableSet{Int64}(Automa.StableDict(1=>nothing,6=>nothing,8=>nothing,3=>nothing,2=>nothing,12=>nothing,13=>nothing))
        if cs == 1
        else 
            if cs == 6
                emit(:float)
            else 
                if cs == 8
                    emit(:float)
                else 
                    if cs == 3
                        emit(:dec)
                    else 
                        if cs == 2
                            emit(:dec)
                        else 
                            if cs == 12
                                emit(:hex)
                            else 
                                if cs == 13
                                    emit(:oct)
                                else 
                                    ()
                                end
                            end
                        end
                    end
                end
            end
        end
        cs = 0
    end
end

今後の動向

Juliaの人気

TIOBE(August 2017)では46位 (https://www.tiobe.com/tiobe-index/)

  • HaskellやGroovyの上
  • Lua, Kotlin, Clojureよりは下

IEEE Spectrum 2017では31位 (http://spectrum.ieee.org/static/interactive-the-top-programming-languages-2017)

  • Clojure, Kotlin, Groovyより上
  • Haskell, Luaより下

Juliaの開発状況

  • Juliaの最新版はv0.6.0
  • GitHubのスター数は9,000を超えた
  • 600人以上がJulia本体にコントリビュート
  • 過去1ヶ月で200以上のプルリクがマージされ、150以上のイシューがクローズされた
  • 1500以上のパッケージ (打ち捨てられたものもあるが...)

Juliaの開発体制

NumFOCUSの支援プロジェクトの1つ

Julia Computing Inc.というJuliaの開発者達による企業が作られた

Julia 1.0?

  • まだ未定だが、運が良ければ今年か、来年に出る?
  • Juliaのリリースは毎回半年ぐらい遅れるので、多分来年の上旬くらいか
  • Julia 1.0が出れば、とりあえず数年は安定して使えそう

入りそうな機能

  • Pkg3: パッケージシステムのメジャーアップデート
    • 現在のPkg2がデカいレポジトリをガンガン書き換えながら管理するので色々問題が
    • Pkg2では、パッケージのバージョンを固定した環境などが作りにくい
    • Pkg3では、分散管理・TOMLによるバージョン固定など使いやすくなる予定
    • https://github.com/JuliaLang/Juleps/blob/master/Pkg3.md
  • NamedTuple: 名前付きタプル
  • Null値: 欠損値の扱いの向上
    • 現在Nullableでnullかもしれない値を表現
    • 配列とかに入れるとちょっと面倒 (いちいちNullableを外さないといけない)
    • Union{Float32,Void}みたいなのが高速に扱えるようになる
    • https://github.com/JuliaLang/julia/pull/22441 (マージ済み)
  • Parallel Task Runtime: マルチスレッド機能の本気実装
    • Juliaの同時実行できるタスクは1つだけ
    • @threadsを使えばforループを局所的に並列実行できるが...
    • タスクを別スレッドで並列実行できるようになりそう
    • https://github.com/JuliaLang/julia/pull/22631

(おまけ) 作ったパッケージの紹介