Сбой памяти при транспонировании [(K,[V])] в [(V,[K])]

У меня есть файл размером 279 МБ, который содержит ~10 миллионов пар ключ/значение с ~500 000 уникальных ключей. Он сгруппирован по ключу (каждый ключ нужно записать только один раз), поэтому все значения для данного ключа находятся вместе.

Что я хочу сделать, так это перенести ассоциацию, создать файл, в котором пары сгруппированы по значению, а все ключи для данного значения хранятся вместе.

В настоящее время я использую Parsec для чтения пар в виде списка кортежей (K,[V]) (используя ленивый ввод-вывод, поэтому я могу обрабатывать его как поток, пока 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)

Я мог бы подумать о паре вещей, которые я делаю неправильно:

  1. 2 МБ кажется маловато (значительно меньше 2 ГБ ОЗУ, установленных на моей машине), так что, может быть, мне нужно сказать GHC, что можно использовать больше?
  2. Мои проблемы могут быть связаны с алгоритмами/структурой данных. Может быть, я использую неправильные инструменты для работы?
  3. Моя попытка использовать ленивый ввод-вывод может обернуться против меня.

Я склоняюсь к (1) на данный момент, но я никоим образом не уверен.


person rampion    schedule 06.12.2012    source источник
comment
Каков объем памяти приложения при возникновении этой ошибки?   -  person asm    schedule 06.12.2012
comment
Эндрю Майерс: только что попробовал +RTS -sstderr -RTS - это работает, когда я C-c останавливаю исполняемый файл досрочно, но если я жду возникновения ошибки, информация о профилировании не печатается. Любые предложения о том, как сообщить об объеме памяти во время ошибки?   -  person rampion    schedule 06.12.2012
comment
Я не думаю, что (1) является проблемой. Да, он не пытается выделить 2 МБ, но это, вероятно, потому, что это новый фрагмент, т. Е. Он уже выделил всю доступную память, и эти 2 МБ - это то, что заставляет все это идти вверх животом.   -  person us2012    schedule 06.12.2012
comment
(2) Если вам нужна только транспонированная карта, почему бы не создать ее во время чтения файла, а не после?   -  person AndrewC    schedule 06.12.2012
comment
(2) Если это парсер из вашего другого вопроса, и он делает то, что вы хотите, вы можете значительно упростить или изменить для более тщательной проверки.   -  person AndrewC    schedule 06.12.2012
comment
Как сказал Эндрю, если вам не нужны обе карты (но с 2 ГБ ОЗУ это может быть слишком много в любом случае, есть довольно много накладных расходов), вы должны создать транспонированную карту напрямую. И transpose' m (Cons (k,vs) xs) = transpose' (L.foldl' (include k) m vs) xs создаст огромные санки, так как transpose' не является строгим в аргументе Map, поэтому foldl' не будет оцениваться до конца. Сделать transpose' строгим в аргументе Map может быть достаточно, но, возможно, потребуется больше.   -  person Daniel Fischer    schedule 06.12.2012
comment
Даниэль Фишер: Я думал, что создаю только одну карту. Я пытаюсь просто читать по одному (K,[V]) из файла за раз и вставлять его в свою карту, только с ErrList, действующим как промежуточный поток, чтобы я мог разделить проблемы. Но, возможно, я терплю неудачу в этом.   -  person rampion    schedule 06.12.2012
comment
@rampion Мне просто интересно, что вы видите в любом мониторе процессов, который у вас есть в вашей системе. Например, что такое столбец RES в топ-шоу?   -  person asm    schedule 06.12.2012
comment
Даниэль Фишер: Что ж, если сделать transpose' строгим в аргументе карты, то мне потребовалось гораздо больше времени, пока я не достиг сбоя памяти.   -  person rampion    schedule 07.12.2012
comment
Эндрю Мейерс: время до отказа составляет несколько часов, это немного больше, чем я хочу потратить на просмотр топа :)   -  person rampion    schedule 07.12.2012
comment
Хорошо, я добавил индикатор выполнения, чтобы я мог видеть, как он работает, и дал ему поработать пару часов, пока он не обработает ~ 25% данных и довольно хорошо зависнет. Код, результаты профилирования и журнал выполнения находятся здесь. Кажется, я использую довольно много памяти, но я не знаю, как определить, сколько из этого находится в HashMap, а сколько потрачено впустую.   -  person rampion    schedule 08.12.2012
comment
Если бы вы могли указать формат файла, я мог бы попробовать его.   -  person tibbe    schedule 08.12.2012
comment
Нужна дополнительная информация о входном файле.   -  person Don Stewart    schedule 24.02.2013
comment
@DonStewart: я tar отредактировал и gzip отредактировал, и загрузил сюда. Это category в строке, за которым следует ноль или более строк rank. url. Я даю немного больше деталей в моем коде.   -  person rampion    schedule 25.02.2013
comment
Вы знаете, что для того, чтобы повернуть объект, есть 2 метода: либо повернуть объект, либо повернуть перспективу.. подумайте об этом   -  person Khaled.K    schedule 07.04.2013


Ответы (2)


Есть вероятность, что данные увеличатся? Если да, то я бы предложил не читать файл while в память и обрабатывать данные другим способом.

Одна простая возможность — использовать для этого реляционную базу данных. Это было бы довольно просто — просто загрузите свои данные, создайте правильный индекс и отсортируйте их по своему усмотрению. База данных сделает всю работу за вас. Я определенно рекомендую это.


Другой вариант — создать собственный файловый механизм. Например:

  1. Выберите некоторый предел l, который разумно загрузить в память.
  2. Создайте n = d `div` l файлов, где d — это общий объем ваших данных. (Надеюсь, это не превысит лимит вашего файлового дескриптора. Вы также можете закрывать и открывать файлы после каждой операции, но это сделает процесс очень медленным.)
  3. Последовательно обработайте ввод и поместите каждую пару (k,v) в файл с номером hash v `mod` l. Это гарантирует, что пары с одинаковым значением v окажутся в одном и том же файле.
  4. Обрабатывайте каждый файл отдельно.
  5. Объедините их вместе.

По сути, это хеш-таблица с сегментами файлов. Это решение предполагает, что каждое значение имеет примерно одинаковое количество ключей (иначе некоторые файлы могут стать очень большими).


Вы также можете реализовать внешнюю сортировку, которая позволит вам сортировать практически любой объем данных.

person Petr    schedule 18.04.2013

Чтобы разрешить файлы, размер которых превышает объем доступной памяти, рекомендуется обрабатывать их небольшими порциями за раз.

Вот надежный алгоритм копирования файла A в новый файл B:

  • Создайте файл B и заблокируйте его на своем компьютере.
  • Begin loop
    • If there isn't a next line in file A then exit loop
    • Читать в следующей строке файла A
    • Применить обработку к строке
    • Проверьте, содержит ли файл B строку уже
    • Если файл B уже не содержит строку, добавьте строку в файл B.
    • Перейти к началу цикла
  • Разблокировать файл B

Также может быть полезно сделать копию файла A во временную папку и заблокировать ее на время работы с ней, чтобы другие люди в сети не могли изменить оригинал, но у вас был снимок файла в том виде, в котором он был. в момент начала процедуры.

Я намерен вернуться к этому ответу в будущем и добавить код.

person WonderWorker    schedule 18.04.2013