概要
[Julia] 多引数関数の速度比較
でコメントを頂いてやり直した結果、違った結論になったので書き直します。
やりたいことは、様々なタイプの多数の引数を関数に渡すのに、速度を落とさず見やすく書きやすい方法を知りたい、ということです。
前回と同じく、
- 全部引数で直接渡す
- 構造体にまとめて1つの引数で渡す
- 辞書型リストで渡す
を比べます。
結論
@antimon2 さん、@bicycle1885 さんのおすすめの通り、
渡したいパラメータを辞書型リストにまとめ、キーワード引数で関数に渡す。
がベストと思いました(最速ではないですが使い勝手良い)。
教えてくださってありがとうございました。
比較
直接引数渡す
前回のやつをちょっとアレンジして、こんな関数。
元はループの中z=
だったんですが、最適化で1回しか計算しないと指摘をいただきました。
一連のコメントで皆さん和を取ってましたので、そのまま使わせてもらいます。
ついでに意味もなく"ans:"つけて、雑多な感じにしてます。
function f1(x::Real,y::Real,z::ComplexF64=complex(0.0),temper::Float64=24.0,name::String="ans:")
# dummy calculation
for i in 1:100000
z += complex(x,y) + temper
end
name*string(z)
end
@btime f1(1,2.5)
210.131 μs (8 allocations: 416 bytes)
"ans:2.5e6 + 250000.0im"
Real
とかのAbstract typeよりもFloat64
といった確定タイプを使うとより速くなるというご指摘もありましたが、直接引数渡している分には、あまり変化無いようです↓。
function f1a(x::Float64,y::Float64,z::ComplexF64=complex(0.0),temper::Float64=24.0,name::String="ans:")
# dummy calculation
for i in 1:100000
z += complex(x,y) + temper
end
name*string(z)
end
@btime f1a(1.0,2.5)
168.368 μs (8 allocations: 416 bytes)
うちの古いPCで40μsぐらいの差。
勿論、わざわざAbstractにするメリットは(大抵?)無いので、型に気を配るべきなんだなーと改めて思う次第。
構造体
わざとAbstractで作ります。
mutable struct St1
x::Real
y::Real
z::Complex
temper::Float64
name::String
end
mutableじゃないほうが高速だとの指摘もありましたが、計算途中でパラメータを更新したい状況を想定してmutableにしてます。(今回は書き換えちゃうと@btime
で複数回呼ばれることで違う計算になるので書き換えはしてません。)
構造体渡しの関数。
function f2(s::St1)
# dummy calculation
z = s.z
for i in 1:100000
z += complex(s.x,s.y) + s.temper
end
s.name*string(z)
end
引数にする構造体実体を作って渡します。
st1 = St1(1,2.5,0+0im,24.0,"ans:")
@btime f2(st1)
21.203 ms (400008 allocations: 10.68 MiB)
$
つけて呼んだ方が良いというご指摘もあったんですが、あまり変化なかったです。というか逆にわずかながら遅くなった?
@btime f2($st1)
22.085 ms (400008 allocations: 10.68 MiB)
構造体渡しだと100倍ぐらい遅くなりますが、Abstract型を使わなければ、直接引数方式と遜色ないようです。↓
mutable struct St2
x::Float64
y::Float64
z::ComplexF64
temper::Float64
name::String
end
st2 = St2(1,2.5,0,24.0,"ans:")
function f2a(s::St2)
# dummy calculation
z = s.z
for i in 1:100000
z += complex(s.x,s.y) + s.temper
end
s.name*string(z)
end
@btime f2a(st2)
168.336 μs (8 allocations: 416 bytes)
型、大事。
構造体にAbstractなのはダメってことですね。
メンバーアクセスのオーバーヘッドはほぼ無いと思って良いのかな。
辞書型
Dict
にして引数をまとめます。ここでもわざと型に無頓着な書き方。
dct = Dict(:x=>1,:y=>2.5,:z=>complex(0),:temper=>24.0,:name=>"ans:")
function f3(d)
# dummy calculation
z = d[:z]
for i in 1:100000
z += complex(d[:x],d[:y]) + d[:temper]
end
d[:name]*string(z)
end
@btime f3(dct)
29.167 ms (300008 allocations: 9.16 MiB)
劇的に違うわけじゃないですが、構造体よりさらに遅い。
ところが、キーワード引数渡しができるんですね。知りませんでした。
function f4(;x::Real,y::Real,z::Complex,temper::Float64,name::String)
# dummy calculation
for i in 1:100000
z += complex(x,y) + temper
end
name*string(z)
end
@btime f4(;dct...)
249.899 μs (25 allocations: 1.64 KiB)
ほぼ直接引数渡しと同じ速度です。
関数のAbstract型をやめればもう少し速いでしょう。
引数直接記入する方式の場合、パラメーター数が増えた時などのコードの書き換えが面倒なので、構造体とか辞書型にまとめたいのですが、うっかりすると型変換で遅くなるので気を使います。
それが、キーワード変数渡しにするだけで解決するのでありがたいです。
ドキュメント読んでも、デフォルト付き引数とキーワード引数の使い分け方がイマイチわからなかったんですが、こういう風に使うんですね。