2015-12-18 15 views
7

私は次のコードでの行で、ファイル(16メガバイト)の行をハッシュしようとしている:大容量ファイルとハッシュ - パフォーマンスの懸念

def hash(data, protocol) do 
    :crypto.hash(protocol, data) 
    |> Base.encode16() 
end 

File.stream!(path) 
|> Stream.map(&hash(&1, :md5) <> "h") 
|> Enum.to_list() 
|> hd() 
|> IO.puts() 

timeコマンドによると、これは10〜12秒かかり、これは、私は次のPythonコードであることを考慮する膨大な数のようだ:約2.3秒で

import md5 

with open('a', 'r') as f: 
    content = f.readlines() 
    l = [] 
    for _, val in enumerate(content): 
     m = md5.new() 
     m.update(val) 
     l.append(m.hexdigest() + "h") 

    print l[0] 

実行(まだtimeによります)。

私はエリクシールコードのパフォーマンスを向上させるためにどのようにしたいですか?

File.stream!(path) 
|> Stream.chunk(chunk_size) # with chunk_size being (nb_of_lines_in_file/10) 
|> Enum.map(fn chunk -> Task.async(fn -> Enum.map(chunk, &hash(&1, :md5) <> "h") end) end) 
|> Enum.flat_map(&Task.await/1) 
|> hd() 
|> IO.puts() 

が、それは実行するために、およそ11+秒、偶数またはより悪い結果が得られ、その理由は次のとおりです。私は10のチャンクに最初のストリームを分割し、それぞれの非同期タスクを発射することを試みましたか?

+3

1)ハッシュラインを削除すると、最初のパフォーマンスがどのように異なるのですか? 2)そして、あなたが2番目の例で行うことを反映するように、 'hash'の定義を変更するとどうなりますか? 3)あなたのコードはハッシュをまったく使用しないので、ループ本体全体を最適化することは許されます。 Pythonはそれを利用できないかもしれませんが、計算の結果が実際に使用されることを保証することは、まだ良い習慣です。 – CodesInChaos

+0

約3)については、3つのコードとそのそれぞれのランタイムを私の質問で更新しました。そのため、すべてのハッシュを使用して文字を追加し、最後に最初のハッシュされた行を印刷します。約1)、実際には、ハッシングなしで私の最初のコードのための巨大なperf boostがあり、それは約4秒で実行されます。 – Kernael

+0

4)最初は 'crypto.hash'を使用し、2番目は' md5'を直接使用します。それはパフォーマンスの違いに責任がありますか? 5)あなたのラインはどれくらい平均的ですか? – CodesInChaos

答えて

2

エリクシールコードのパフォーマンスを記録するために時間を使用すると、BEAM仮想マシンの起動時間が常に になることが考慮されます。 アプリケーションによっては、 の他の言語との比較ベンチマークにこれを含めることは意味があるかもしれません。 エリクシールコードのパフォーマンスを最大限に引き上げたい場合は、Benchfellaのようなベンチマーキング ツールを使用することをお勧めします。また、erlangのtimer.tcだけを使用することもできます。

https://hex.pm/packages/benchfella

私の推測では、あなたのパフォーマンスの問題は、すべてのI/Oに関連しているということです。 File.stream!は、大きなファイルの行処理に特に効率的ではありません。

私はファイル全体をハッシュする同様の問題についてブログ投稿を書いています。

http://www.cursingthedarkness.com/2015/06/micro-benchmarking-in-elixir-using.html

そしてここで、高速ラインベースの処理を行うことについてのスライドの話があります。

http://bbense.github.io/beatwc/

私はあなたがあなたの中にファイル全体を読ま場合は、より良いパフォーマンスを得ると思います。私はちょうど16メガバイトのファイルを

File.stream!(path) |> Enum.map(fn(line) -> hash(line, :md5) <> "h" end) 

を使用するために、まったく躊躇しないだろう。パイプラインでストリームを使用すると、ほとんどの場合、メモリ使用のスピードが変わります。 Elixirではデータが不変であるため、大規模なリストのオーバーヘッドは通常、最初に予想していたよりも少なくなります。

あなたのタスクベースのコードは、私が の時間の大部分がこれら2行の行をチャンクするのに費やされていると思われるので、あまり役に立ちません。

File.stream!(path) 
|> Stream.chunk(chunk_size) # with chunk_size being (nb_of_lines_in_file/10) 

これは非常に遅くなるでしょう。他にも便利なコード例があります。 https://github.com/dimroc/etl-language-comparison/tree/master/elixir

エリクシルで高速ファイル処理を行うために使用できる多くのトリックがあります。あなたはしばしば純粋なFile.stream!バージョンからのスピードを複数の桁で向上させることができます。

+0

偉大な答え。ファイル全体をメモリにロードするだけでさらに高速になるかもしれないことを指摘する価値があります。これらの抽象概念は、実際には大きなコレクションやファイルを扱う場合には適していますが、小さなものについてはオーバーヘッドが増えます。 –

+0

私はFile.streamをテストしました! |> Enum.to_listファイル全体を読み込み、次にbinary.splitを使用し、28メガの1000_000行ファイルの場合はランタイムが似ています。この問題では、I/O時間が唯一の制約です。一般的なテキストファイルよりも長くなります。 –

関連する問題