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 でも良いか? なんて気になってしまいます。

コメント

  1. 複数箇所で使われている
    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)

    となりました。

    返信削除
    返信
    1. Shuhei Kishi さん、コメントありがとうございます。

      julia は、for ループが、なんとかかければ、基本的には実用性が高いので、大好きです。
      もともと、早い julia ですが、わずかでも早くなると、とても楽しいです。

      vcat() や push に時間がかかることも教えていただき感謝です。
      ひたすら独学ですので、ご意見とてもありがたいです。

      先に入れものの Array を作ってからというのは、とても、勉強になりました。
      いろいろ、push で、がんばっていましたので、応用して速度を上げたいと考えます。
      JSON 形式から、DataFrame または Array への変換も、ひたすら vcat() だと速度が出ず困っていました。

      また、Array(df[collect(df[:,2].>0) .& collect(df[:,2].>0),:]) の間違いは、ご指摘のとおりです。
      速度を見るには、影響は無いのですが、初歩的な間違いのご指摘ありがとうございます。
      ブログを訂正する方法と、しない方法があると思います。しばらくは、せっかくのコメントが理解できなくなるので、そのままにしておきます。

      今後とも、よろしくお願いいたします。

      削除

コメントを投稿

このブログの人気の投稿

Image J で特定の色域の面積を測る方法

LaTeX 温度表現

Rで、条件 (時に複数条件) にあうデータを取り出す方法