2015-12-18 23 views
7

Sto cercando di hash di un file riga (16 MB) per riga con il seguente codice prestazioni:File di grandi dimensioni e hashing - preoccupazione

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

Secondo time comando, questo richiede tra i 10 ei 12 secondi, che sembra essere un numero enorme per, me visto che con il seguente codice Python:

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] 

piste (sempre secondo time) in circa 2,3 secondi.

Dove dovrei stardare per migliorare le prestazioni del mio codice Elixir? Ho cercato di dividere il flusso iniziale in 10 pezzi, e fuoco un'attività asincrona per ciascuno di essi:

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

ma i rendimenti pari o peggiori risultati, circa 11+ secondi per eseguire, perché?

+3

1) Come differiscono le prestazioni del primo se si rimuove la linea di hashing? 2) E se modificassi la tua definizione di 'hash' in modo che rispecchi ciò che fai nel secondo esempio? 3) Il tuo codice non usa affatto l'hash, quindi sarebbe lecito ottimizzare l'intero corpo del loop. Mentre python potrebbe non essere in grado di trarne vantaggio, è comunque buona pratica assicurarsi che il risultato dei calcoli venga effettivamente utilizzato. – CodesInChaos

+0

Informazioni su 3), ho aggiornato i tre codici e il rispettivo runtime nella mia domanda in modo che tutti facciano uso degli hash aggiungendo un carattere a loro e stampando la prima riga con hash alla fine. A proposito di 1), c'è davvero un enorme boost di perf per il mio primo codice senza hashing, funziona in circa 4 secondi. – Kernael

+0

4) Il primo usa 'crypto.hash' il secondo usa direttamente' md5'. Questo è responsabile della differenza di prestazioni? 5) Quanto durano le tue battute su averabe? – CodesInChaos

risposta

2

Una cosa da tenere in considerazione è che l'utilizzo del tempo per registrare le prestazioni del codice Elixir prende sempre in considerazione lo l'ora di avvio della macchina virtuale BEAM. In base all'applicazione , potrebbe essere o non avere senso includerla in qualsiasi benchmark di confronto in altre lingue. Se vuoi solo aumentare le prestazioni del codice Elixir, è meglio usare uno strumento di benchmarking come Benchfella o anche solo: timer.tc da erlang.

https://hex.pm/packages/benchfella

La mia ipotesi è che i problemi di prestazioni sono tutti gli I/O correlato. File.stream! non è particolarmente efficiente per l'elaborazione di righe di file di grandi dimensioni.

Ho scritto un post sul blog su un problema simile di hashing dell'intero file.

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

E c'è un discorso scorrevole di fare l'elaborazione basata linea veloce qui.

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

Penso che se si slurp l'intero file in si otterrà una migliore performance. Non esiterei affatto a usare solo

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

per un file 16mb. L'uso di un flusso in una pipeline traduce quasi sempre la velocità per l'utilizzo della memoria. Poiché i dati sono immutabili in Elixir, le liste di grandi dimensioni in genere hanno un sovraccarico inferiore rispetto a quanto inizialmente previsto.

Il codice basato su attività non sarà di grande aiuto poiché sospetto che la maggior parte del tempo di venga speso per il chunk delle linee in queste due linee.

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

Questo sarà molto lento. Un altro esempio di codice che potresti trovare utile. https://github.com/dimroc/etl-language-comparison/tree/master/elixir

Ci sono molti trucchi che è possibile utilizzare per ottenere un'elaborazione veloce dei file in elisir. Spesso puoi migliorare la velocità dalla versione originale di File.stream! di più ordini di grandezza.

+0

Ottima risposta. Vale la pena sottolineare che potrebbe essere ancora più veloce semplicemente caricando l'intero file in memoria. Queste astrazioni sono ottime quando si lavora con raccolte o file veramente grandi, per quelli piccoli, aggiunge davvero un sovraccarico. –

+0

Ho testato File.stream! |> Enum.to_list contro lettura nell'intero file e quindi utilizzando binary.split e per un file di linea da 28 mega, 1000_000 il runtime è simile.Per questo problema, l'I/O time è l'unico vincolo reale, a meno che le righe non siano molto più lungo di un tipico file di testo. –

Problemi correlati