julia で、n×m の Array で平均等の計算の速度を上げる方法
先日、juliaDB は、早いという話しを書きました。しかしながら、Array を、しっかりと使いこなすと、Array はとても早いです。
先日の DataFrame を作ってみましょう。
julia> df = DataFrame(
col1 = randn(10^5),
col2 = randn(10^5),
col3 = randn(10^5)
);
この DataFrame の横方向の合計等を調べようというお話しです。
単純に、横方向の合計を求める場合は、まず、Array に変換して、sum(A::Array,2) とするだけで良いようです。
julia 1.0 の人は、sum(A::Array, dims=2) なので注意です。
julia> A = Array(df);
julia> sum(A,2) # julia 1.0 のひとは、"sum(A, dims=2)" です
100000×1 Array{Float64,2}:
0.339824
-0.711329
5.8185
-1.94555
0.535156
-0.670403
-1.56872
0.534236
-1.98732
-2.90369
⋮
略
これで1撃です。mean(), std()なども、この方法が使えます。
では、先日の関数、
julia> function summary_row(df::DataFrame)
array_selected = Array(df[collect(df[:,2].>0) .& collect(df[:,2].>0),:])
row_sum=Array{Float64,1}[]
row_mean=Array{Float64,1}[]
for i in 1:size(array_selected)[1]
if i==1
row_sum = sum(array_selected[i,:])
row_mean = mean(array_selected[i,:])
else
row_sum = vcat(row_sum, sum(array_selected[i,:]) )
row_mean = vcat(row_mean, mean(array_selected[i,:]) )
end
end
return DataFrame(sum=row_sum, mean=row_mean)
end
上記のかなり正直な組み方だと、10秒以上かかっていました。個人的には、現在、一番時間を取るのは、vcat() のところだと考えています。
これを組み変えてみます。
function summary_row_A(df::DataFrame)
array_selected = Array(df[collect(df[:,2].>0) .& collect(df[:,2].>0),:])
row_sum = sum(array_selected, 2)
row_mean = mean(array_selected, 2)
row_std = std(array_selected, 2)
return DataFrame(sum=row_sum[1:end], mean=row_mean[1:end], std=row_std[1:end])
end
こうなります。
では、これを動かしてみましょう。
julia> @time summary_row_A(df); 0.075172 seconds (18.31 k allocations: 6.151 MiB) julia> @time summary_row_A(df); 0.007241 seconds (220 allocations: 5.213 MiB)
すばらしく早くなりました。Array の使い方は難しいですね。
では、10^6 個に増やしてやってみます。
julia> df = DataFrame(
col1 = randn(10^6),
col2 = randn(10^6),
col3 = randn(10^6)
);
julia> @time summary_row_A(df);
0.099327 seconds (223 allocations: 51.933 MiB, 30.86% gc time)
julia> @time summary_row_A(df);
0.213423 seconds (223 allocations: 51.933 MiB, 68.19% gc time)
julia> @time summary_row_A(df);
0.071288 seconds (223 allocations: 51.933 MiB, 16.10% gc time)
とても早いです。
ようやく julia 1.0.0 の公式マニュアルから mean() などで、計算の方向が2番目の引数で指定できることが記載されるようになりました。しかし、こうも、Array と DataFrame で速度が異ると、どうしても csv 形式で保存したい時以外は、Array のまま、列名は別の Array か Dictionary でも良いか? なんて気になってしまいます。
複数箇所で使われている
返信削除Array(df[collect(df[:,2].>0) .& collect(df[:,2].>0),:])
の部分は
Array(df[collect(df[:,1].>0) .& collect(df[:,2].>0),:])
でしょうか。(”爆速の JuliaDB”の記事の方に”1列目と2列目が正の行を選び、行の平均を求めます”という記述があったので)
Juliaの速さを損ねずに書くのって面白いですよね。vcat()やpushは基本的に遅いので代入にするが吉ですね。sumやmeanの方向を指定せずにforループを使用したまま早くするのであれば、
function summary_row_B(df::DataFrame)
array_selected = Array(df[collect(df[:,2].>0) .& collect(df[:,2].>0),:])
row_number = nrow(df)
row_sum=Array{Float64,1}(row_number)
row_mean=Array{Float64,1}(row_number)
for i in 1:size(array_selected)[1]
row_sum[i] = sum(array_selected[i,:])
row_mean[i] = mean(array_selected[i,:])
end
return DataFrame(sum=row_sum, mean=row_mean)
end
と代入形式に書き換えることができて、僕の環境で時間計測をすると
@time summary_row(df)
@time summary_row_A(df)
@time summary_row_B(df)
16.019838 seconds (5.75 M allocations: 18.848 GiB, 12.27% gc time)
0.776705 seconds (531.01 k allocations: 33.886 MiB, 0.98% gc time)
0.821702 seconds (1.37 M allocations: 40.083 MiB, 1.50% gc time)
となりました。
Shuhei Kishi さん、コメントありがとうございます。
削除julia は、for ループが、なんとかかければ、基本的には実用性が高いので、大好きです。
もともと、早い julia ですが、わずかでも早くなると、とても楽しいです。
vcat() や push に時間がかかることも教えていただき感謝です。
ひたすら独学ですので、ご意見とてもありがたいです。
先に入れものの Array を作ってからというのは、とても、勉強になりました。
いろいろ、push で、がんばっていましたので、応用して速度を上げたいと考えます。
JSON 形式から、DataFrame または Array への変換も、ひたすら vcat() だと速度が出ず困っていました。
また、Array(df[collect(df[:,2].>0) .& collect(df[:,2].>0),:]) の間違いは、ご指摘のとおりです。
速度を見るには、影響は無いのですが、初歩的な間違いのご指摘ありがとうございます。
ブログを訂正する方法と、しない方法があると思います。しばらくは、せっかくのコメントが理解できなくなるので、そのままにしておきます。
今後とも、よろしくお願いいたします。