2016-05-14 19 views
5

Sto cercando di analizzare un file con un milione di righe, ogni riga è una stringa json con alcune informazioni su un libro (autore, contenuto ecc.). Sto usando iota per caricare il file, poiché il mio programma lancia un OutOfMemoryError se provo a usare slurp. Sto anche usando cheshire per analizzare le stringhe. Il programma carica semplicemente un file e conta tutte le parole in tutti i libri.Perché pmap | reducers/map non usa tutti i core della CPU?

Il mio primo tentativo ha incluso pmap per eseguire il lavoro pesante, ho pensato che essenzialmente avrebbe utilizzato tutti i miei core della CPU.

(ns multicore-parsing.core 
    (:require [cheshire.core :as json] 
      [iota :as io] 
      [clojure.string :as string] 
      [clojure.core.reducers :as r])) 


(defn words-pmap 
    [filename] 
    (letfn [(parse-with-keywords [str] 
      (json/parse-string str true)) 
      (words [book] 
      (string/split (:contents book) #"\s+"))] 
    (->> 
    (io/vec filename) 
    (pmap parse-with-keywords) 
    (pmap words) 
    (r/reduce #(apply conj %1 %2) #{}) 
    (count)))) 

Anche se sembra utilizzare tutti i core, ogni core utilizza raramente più del 50% della sua capacità, la mia ipotesi è che ha a che fare con la dimensione del lotto di pmap e così mi sono imbattuto in relatively old question dove alcuni commenti fare riferimento alla libreria clojure.core.reducers.

ho deciso di riscrivere la funzione utilizzando reducers/map:

(defn words-reducers 
    [filename] 
    (letfn [(parse-with-keywords [str] 
      (json/parse-string str true)) 
      (words [book] 
      (string/split (:contents book) #"\s+"))] 
    (->> 
    (io/vec filename) 
    (r/map parse-with-keywords) 
    (r/map words) 
    (r/reduce #(apply conj %1 %2) #{}) 
    (count)))) 

Ma l'utilizzo della CPU è peggio, e ancora richiede più tempo per terminare rispetto alla precedente realizzazione:

multicore-parsing.core=> (time (words-pmap "./dummy_data.txt")) 
"Elapsed time: 20899.088919 msecs" 
546 
multicore-parsing.core=> (time (words-reducers "./dummy_data.txt")) 
"Elapsed time: 28790.976455 msecs" 
546 

Che cosa sono io fare male? Mmap sta caricando + riduzioni l'approccio corretto durante l'analisi di un file di grandi dimensioni?

MODIFICA: this è il file che sto utilizzando.

EDIT2: Qui ci sono i tempi con iota/seq invece di iota/vec:

multicore-parsing.core=> (time (words-reducers "./dummy_data.txt")) 
"Elapsed time: 160981.224565 msecs" 
546 
multicore-parsing.core=> (time (words-pmap "./dummy_data.txt")) 
"Elapsed time: 160296.482722 msecs" 
546 
+1

Sembra 'io/vec' scansiona l'intero file per costruire un indice di dove le linee sono. Ottenete risultati diversi se provate 'io/seq'? –

+0

@NathanDavis Ho appena provato, i tempi sono peggiori. fammi aggiornare la domanda – eugecm

+1

[Questo talk] (https://www.youtube.com/watch?v = BzKjIk0vgzE) di Leon Barrett, autore di [Claypoole] (https://github.com/TheClimateCorporation/claypoole), potrebbe avere alcune informazioni rilevanti. Spiega 'pmap' in dettaglio, incluso il motivo per cui spesso non satura la CPU, e un po 'sul perché nutrire un' pmap' in un altro può avere risultati sorprendenti. Inoltre, se sei principalmente alla ricerca di un modo per saturare la tua CPU, Claypoole potrebbe essere esattamente ciò di cui hai bisogno. –

risposta

2

Non credo che riduttori stanno per essere la soluzione giusta per voi, in quanto non affrontare le sequenze pigri in tutto bene (un riduttore darà risultati corretti con una sequenza pigra, ma non andrà bene parallelamente).

Si potrebbe dare un'occhiata a questo sample code dal libro Seven Concurrency Models in Seven Weeks (disclaimer: io sono l'autore) che risolve un problema simile (contando il numero di volte che ogni parola appare su Wikipedia).

Dato un elenco di pagine di Wikipedia, questa funzione conta le parole in sequenza (get-words restituisce una sequenza di parole da una pagina):

(defn count-words-sequential [pages] 
    (frequencies (mapcat get-words pages))) 

Questa è una versione parallela utilizzando pmap che non correre più veloce, ma solo intorno 1,5 volte più velocemente:

(defn count-words-parallel [pages] 
    (reduce (partial merge-with +) 
    (pmap #(frequencies (get-words %)) pages))) 

la ragione per cui si va solo circa 1,5 volte più velocemente è perché il reduce diventa un collo di bottiglia, che sta chiamando (partial merge-with +) una volta per ogni pagina. Unione di lotti di 100 pagine migliora le prestazioni a circa 3.2x su una macchina a 4 core:

(defn count-words [pages] 
    (reduce (partial merge-with +) 
    (pmap count-words-sequential (partition-all 100 pages)))) 
+0

era 'pages' una sequenza lazy? o è stato precedentemente caricato con tutte le pagine? – eugecm

+0

'pages' è pigro, sì. –

+0

Puoi vedere la fonte che carica le pagine qui: https://media.pragprog.com/titles/pb7con/code/FunctionalProgramming/WordCount/src/wordcount/pages.clj e per completezza l'implementazione di get-words qui: https : //media.pragprog.com/titles/pb7con/code/FunctionalProgramming/WordCount/src/wordcount/words.clj –