Сбой памяти при переносе [(K, [V])] в [(V, [K])]
У меня есть файл размером 279 МБ, содержащий ~ 10 миллионов пар ключ/значение, с ~ 500 000 уникальными ключами. Он сгруппирован по ключу (каждая клавиша должна быть записана только один раз), поэтому все значения для данного ключа будут вместе.
Что я хочу сделать, это транспонировать ассоциацию, создать файл, в котором пары сгруппированы по значению, а все ключи для заданного значения сохраняются вместе.
В настоящее время я использую Parsec для чтения в парах в виде списка кортежей (K,[V])
(используя ленивый IO, поэтому я могу обрабатывать его как поток, в то время как Parsec обрабатывает входной файл), где:
newtype K = K Text deriving (Show, Eq, Ord, Hashable)
newtype V = V Text deriving (Show, Eq, Ord, Hashable)
tupleParser :: Parser (K,[V])
tupleParser = ...
data ErrList e a = Cons a (ErrList e a) | End | Err e
parseAllFromFile :: Parser a -> FilePath-> IO (ErrList ParseError a)
parseAllFromFile parser inputFile = do
contents <- readFile inputFile
let Right initialState = parse getParserState inputFile contents
return $ loop initialState
where loop state = case unconsume $ runParsecT parser' state of
Error err -> Err err
Ok Nothing _ _ -> End
Ok (Just a) state' _ -> a `Cons` loop state'
unconsume v = runIdentity $ case runIdentity v of
Consumed ma -> ma
Empty ma -> ma
parser' = (Just <$> parser) <|> (const Nothing <$> eof)
Я попытался вставить кортежи в Data.HashMap.Map V [K]
, чтобы транспонировать ассоциацию:
transpose :: ErrList ParseError (K,[V]) -> Either ParseError [(V,[K])]
transpose = transpose' M.empty
where transpose' _ (Err e) = Left e
transpose' m End = Right $ assocs m
transpose' m (Cons (k,vs) xs) = transpose' (L.foldl' (include k) m vs) xs
include k m v = M.insertWith (const (k:)) v [k] m
Но когда я попробовал, я получил ошибку:
memory allocation failed (requested 2097152 bytes)
Я мог бы подумать о нескольких вещах, которые я делаю неправильно:
- 2MB кажется немного низким (значительно меньше, чем 2 ГБ оперативной памяти, установленной моей машиной), поэтому, возможно, мне нужно сказать GHC, что это нормально, чтобы использовать больше?
- Мои проблемы могут быть связаны с алгоритмической структурой/структурой данных. Может быть, я использую неправильные инструменты для работы?
- Моя попытка использовать ленивый IO может вернуться, чтобы укусить меня.
Сейчас я склоняюсь к (1), но я не уверен ни в коем случае.
Ответы
Ответ 1
Возможно ли, что данные будут увеличиваться? Если да, то я предлагаю не читать файл while в памяти и обрабатывать данные по-другому.
Одна простая возможность - использовать для этого реляционную базу данных. Это довольно просто - просто загрузите свои данные, создайте правильный индекс и отсортируйте его по мере необходимости. База данных сделает всю работу за вас. Я определенно рекомендую это.
Другой вариант - создать собственный файловый механизм. Например:
- Выберите некоторый предел
l
, который разумно загрузить в память.
- Создайте
n = d `div` l
файлы, где d
- общая сумма ваших данных. (Надеюсь, это не будет превышать лимит дескриптора файла. Вы также можете закрыть и снова открыть файлы после каждой операции, но это сделает процесс очень медленным.)
- Обработать вход последовательно и поместить каждую пару
(k,v)
в номер файла hash v `mod` l
. Это гарантирует, что пары с одинаковым значением v
попадут в один и тот же файл.
- Обработать каждый файл отдельно.
- Объедините их вместе.
Это по существу хэш-таблица с файловыми ковшиками. Это решение предполагает, что каждое значение имеет примерно одинаковое количество ключей (в противном случае некоторые файлы могут быть исключительно большими).
Вы также можете реализовать внешний вид который позволит вам сортировать в основном любой объем данных.
Ответ 2
Чтобы разрешить файлы, размер которых превышает доступную память, рекомендуется обрабатывать их в кусках размером с укусом за раз.
Вот твердый алгоритм для копирования файла A в новый файл B:
- Создайте файл B и заблокируйте его на своем компьютере.
- Начало цикла
- Если в файле A нет следующей строки, то завершите цикл
- Читайте в следующей строке файла A
- Применить обработку к строке
- Убедитесь, что файл B содержит строку уже
- Если файл B не содержит строку уже, добавьте строку в файл B
- Перейти к началу цикла
- Разблокировать файл B
Также полезно сделать копию файла A во временную папку и заблокировать ее во время работы с ней, чтобы другим людям в сети не мешали изменять оригинал, но у вас есть моментальный снимок файла как это было в момент начала процедуры.
Я намерен пересмотреть этот ответ в будущем и добавить код.