Sto elaborando file di testo da 60 GB o più. I file sono separati in una sezione di intestazione di lunghezza variabile e una sezione di dati. Ho tre funzioni:Clojure - elabora file di grandi dimensioni con poca memoria
head?
un predicato per distinguere le linee di intestazione da linee di datiprocess-header
processo un'intestazione spezzataprocess-data
processo spezzata indicatori- Le funzioni di elaborazione asincrono accedere e modificare una database in memoria
Sono avanzato su un metodo di lettura file da un altro thread SO, che dovrebbe creare un sequenza pigra di linee. L'idea era di elaborare alcune linee con una funzione, quindi cambiare la funzione una volta e continuare l'elaborazione con la funzione successiva.
(defn lazy-file
[file-name]
(letfn [(helper [rdr]
(lazy-seq
(if-let [line (.readLine rdr)]
(cons line (helper rdr))
(do (.close rdr) nil))))]
(try
(helper (clojure.java.io/reader file-name))
(catch Exception e
(println "Exception while trying to open file" file-name)))))
Io lo uso con qualcosa come
(let [lfile (lazy-file "my-file.txt")]
(doseq [line lfile :while head?]
(process-header line))
(doseq [line (drop-while head? lfile)]
(process-data line)))
Anche se funziona, è piuttosto inefficiente per un paio di motivi:
- Invece di limitarsi a chiamare
process-head
fino a raggiungere i dati e quindi continuando conprocess-data
, devo filtrare le righe di intestazione e elaborarle, quindi riavviare l'analisi dell'intero file e rilasciare tutte le righe di intestazione per elaborare i dati. Questo è l'esatto contrario di ciò chelazy-file
intendeva fare. - La visualizzazione del consumo di memoria mi mostra che il programma, sebbene apparentemente pigro, si accumula per utilizzare la quantità di RAM necessaria per mantenere il file in memoria.
Quindi, qual è un modo più efficiente e idiomatico per lavorare con il mio database?
Un'idea potrebbe utilizzare un multimetodo per elaborare intestazione e dati dipendenti dal valore del predicato head?
, ma suppongo che ciò avrebbe un impatto serio sulla velocità, specialmente in quanto vi è un solo caso in cui il risultato del predicato cambia da sempre fedele a sempre falso. Non l'ho ancora fatto.
Sarebbe meglio utilizzare un altro modo per creare la seq di riga e analizzarla con iterate
? Questo mi lascerebbe comunque la necessità di usare: while e: drop-while, credo.
Nella mia ricerca, l'accesso al file NIO è stato menzionato un paio di volte, il che dovrebbe migliorare l'utilizzo della memoria. Non sono ancora riuscito a scoprire come usarlo in modo idiomatico in clojure.
Forse ho ancora una cattiva comprensione dell'idea generale, come deve essere trattato il file?
Come sempre, qualsiasi aiuto, idee o suggerimenti per i tut sono molto apprezzati.
Grazie per la risposta. Ieri ho scritto alcuni casi di test per fare benchmarking. Si è scoperto che ** A) ** Non è la lettura stessa che consuma tanta memoria, sembra essere il database (btw, le mie affermazioni sul consumo di memoria derivano dall'esecuzione dell'applicazione compilata) ** B) * * '' 'lazy-file''' e' '' line-seq''' si comportano in modo approssimativo, considerando la velocità e l'uso della memoria ** C) ** Sorprendentemente i metodi multimodali e un approccio loop-recurere richiedono circa il 150% del tempo necessario per aprire il file due volte e utilizzare while/drop-while – waechtertroll
Mi piace il tuo modo di ricorsione durante la lettura del file. La prossima idea che proverò è che avrò il parser dell'header per controllare se la riga successiva è una linea dati (stile iteratore) e, in caso affermativo, trampolino via al parser dei dati. Se-else su ogni riga è molto lento, ma i file sono ben definiti in poche centinaia di righe di intestazione e centinaia di milioni di linee di dati, e la lettura della testa richiede meno di mezzo secondo. Non sono ancora sicuro, come combinare trampolino ed iteratore ... – waechtertroll