Я обрабатываю текстовые файлы размером 60 ГБ или больше. Файлы разделяются на секцию заголовка переменной длины и раздел данных. У меня есть три функции:Clojure - обрабатывать огромные файлы с низкой памятью
head?
Предикат для различения заголовков строк из строк данныхprocess-header
процесса один строка заголовка строкаprocess-data
процесс один линия данных строк- Функции обработки асинхронно доступа и модифицирует in-memory
Я применил метод чтения файлов из другого потока SO, который должен построить ленивая последовательность строк. Идея состояла в том, чтобы обработать некоторые строки с помощью одной функции, затем один раз переключить функцию и продолжить обработку следующей функцией.
(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)))))
Я использую его с чем-то вроде
(let [lfile (lazy-file "my-file.txt")]
(doseq [line lfile :while head?]
(process-header line))
(doseq [line (drop-while head? lfile)]
(process-data line)))
Хотя это работает, это довольно неэффективно по нескольким причинам:
- Вместо того, чтобы просто вызвать
process-head
, пока я не достигну данных и затем, продолжая сprocess-data
, я должен фильтровать строки заголовка и обрабатывать их, а затем перезапускать парсинг всего файла и отбрасывать все строки заголовка для обработки данных. Это полная противоположность тому, что должно было делатьlazy-file
. - Наблюдение за потреблением памяти показывает мне, что программа, хотя и кажущаяся ленивой, позволяет использовать столько оперативной памяти, сколько потребуется для хранения файла в памяти.
Итак, что является более эффективным идиоматическим способом работы с моей базой данных?
Одна идея может использовать мультиметод для обработки заголовка и данных, зависящих от значения предиката head?
, но я полагаю, что это имело бы какое-то серьезное влияние на скорость, тем более что есть только одно место, где результат предиката изменяется от всегда true всегда всегда false. Я еще не оценил это.
Было бы лучше использовать другой способ построения строки-seq и проанализировать ее с помощью iterate
? Это все равно оставит меня нужным использовать: while и: drop-while, я думаю.
В моих исследованиях несколько раз упоминался доступ к файлам NIO, что должно улучшить использование памяти. Я еще не мог узнать, как использовать это в идиоматическом ключе в clojure.
Возможно, у меня все еще плохое понимание общей идеи, как следует обрабатывать файл?
Как всегда, любая помощь, идеи или указатели на tats приветствуются.
Спасибо за ваш ответ. Вчера я написал несколько тестов для проведения бенчмаркинга. Оказалось, что ** A) ** Это не само чтение, которое потребляет столько памяти, кажется, это база данных (кстати, мои требования к потреблению памяти связаны с запуском скомпилированного приложения) ** B) * * '' '' lazy-file''' и '' 'line-seq''' выполняют примерно равные, учитывая скорость и использование памяти ** C) ** Удивительно, что многоточечные методы и подход с циклом-повторами потребуют около 150% времени, необходимого для открытия файла дважды и использования while/drop-while – waechtertroll
Мне нравится ваш способ рекурсии при чтении файла. Следующая идея, которую я попробую, заключается в том, что у меня будет проверка заголовка-парсера, если следующая строка - это строка данных (стиль итератора), и если да, то батут прочь к парсеру данных. Если-else на каждой строке очень медленно, но файлы хорошо определены в несколько сотен строк заголовка и сотни миллионов строк данных, а чтение головы занимает менее половины секунды. Я еще не уверен, как объединить батут и итератор ... – waechtertroll