2015-12-18 9 views
7

próbuję hash plik (16 MB), linia po linii z następującego kodu:duży plik i mieszania - wydajność troska

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() 

Zgodnie z poleceniem time, to trwa od 10 do 12 sekund, który wydaje się być ogromna liczba się, uznając, że mnie z następującego kodu Pythona:

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] 

(nadal działa zgodnie z time) w około 2,3 sekundy.

Gdzie powinienem ustawić, aby poprawić działanie mojego kodu Elixir? Starałem się, aby podzielić początkowego strumienia na 10 kawałków, a ogień asynchroniczne zadanie dla każdego z nich:

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() 

ale daje jeszcze gorsze wyniki, lub około 11+ sekund do uruchomienia, to dlaczego?

+3

1) W jaki sposób wydajność pierwszego różni się po usunięciu linii mieszania? 2) A co, jeśli zmienisz definicję 'hash', tak aby odzwierciedlała to, co robisz w drugim przykładzie? 3) Twój kod w ogóle nie używa skrótu, więc byłoby możliwe zoptymalizowanie całego obiektu pętli. Chociaż python może nie być w stanie tego wykorzystać, nadal dobrą praktyką jest zapewnienie, że wynik obliczeń zostanie faktycznie wykorzystany. – CodesInChaos

+0

Około 3), zaktualizowałem trzy kody i ich odpowiednie środowisko uruchomieniowe w moim pytaniu, aby wszyscy użyli skrótów, dodając do nich znak i drukowac pierwszą haszowaną linię na końcu. Około 1), istnieje rzeczywiście ogromny dopalacz dla mojego pierwszego kodu bez skrótu, działa w około 4s. – Kernael

+0

4) Pierwszy używa 'crypto.hash' drugi używa bezpośrednio' md5'. Czy to jest odpowiedzialne za różnicę wydajności? 5) Jak długie są twoje linie na averabe? – CodesInChaos

Odpowiedz

2

Jedną z rzeczy do wzięcia pod uwagę jest to, że wykorzystanie czasu na nagranie wydajności kodu Elixir zawsze bierze pod uwagę czas rozruchu maszyny wirtualnej BEAM. W zależności od aplikacji może to być uzasadnione, ale nie musi być uwzględnione w jakichkolwiek porównawczych testach porównawczych dla innych języków. Jeśli chcesz tylko uzyskać maksymalną wydajność kodu Elixir, najlepiej użyć narzędzia Benchfella do testowania porównawczego lub nawet samego: timer.tc z erlang.

https://hex.pm/packages/benchfella

Domyślam się, że problemy techniczne są wszystkie I/O powiązany. File.stream! nie jest szczególnie wydajny w przypadku przetwarzania dużych plików w linii.

Napisałem post na blogu o podobnym problemie z hashowaniem całego pliku.

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

I jest rozmowa zjeżdżalnia o zrobieniu przetwarzania opartego szybka linia tutaj.

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

myślę, że jeśli slurp cały plik dostaniesz lepszą wydajność. Nie zawaham się w ogóle po prostu użyć

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

dla pliku 16MB. Używanie strumienia w potoku prawie zawsze handluje prędkością w celu wykorzystania pamięci. Ponieważ dane są niezmienne w Elixir, duże listy mają na ogół mniej narzutów niż początkowo spodziewano.

Twój kod oparty na zadaniach nie pomoże wiele, ponieważ podejrzewam, że większość czasu jest zużywana na dzielenie linii w tych dwóch liniach.

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

To będzie bardzo powolne. Inny przykład kodu, który może Ci się przydać. https://github.com/dimroc/etl-language-comparison/tree/master/elixir

Istnieje wiele trików, których można użyć do szybkiego przetwarzania plików w eliksiru. Często można poprawić prędkość z naiwnej wersji File.stream! o wiele rzędów wielkości.

+0

Świetna odpowiedź. Warto zaznaczyć, że może być jeszcze szybszy, po prostu załadowanie całego pliku do pamięci. Te abstrakcje są świetne, gdy pracujesz z naprawdę dużymi kolekcjami lub plikami, dla małych, to po prostu dodaje narzut. –

+0

Przetestowałem File.stream! |> Enum.to_list vs czytanie w całym pliku, a następnie użycie pliku binary.split i pliku o rozmiarze 28 meg, 1000_000 środowisko wykonawcze jest podobne. Dla tego problemu czas wejścia/wyjścia jest jedynym rzeczywistym ograniczeniem, chyba że linie są dużo dłużej niż w typowym pliku tekstowym. –