2012-03-17 3 views
9

Предположим, у меня есть несколько файлов размером 200 Мбайт, которые я хочу пропустить. Как мне это сделать в Haskell?Разбор больших файлов журнала в Haskell

Вот моя первоначальная программа:

import Data.List 
import Control.Monad 
import System.IO 
import System.Environment 

main = do 
    filename <- liftM head getArgs 
    contents <- liftM lines $ readFile filename 
    putStrLn . unlines . filter (isPrefixOf "import") $ contents 

Это читает весь файл в память перед разбором через него. Тогда я пошел с этим:

import Data.List 
import Control.Monad 
import System.IO 
import System.Environment 

main = do 
    filename <- liftM head getArgs 
    file <- (openFile filename ReadMode) 
    contents <- liftM lines $ hGetContents file 
    putStrLn . unlines . filter (isPrefixOf "import") $ contents 

Я думал, так как hGetContents ленив, it will avoid reading the whole file into memory. Но запуск обоих сценариев под valgrind показал одинаковое использование памяти для обоих. Так что либо мой сценарий ошибочен, либо valgrind ошибочен. Я скомпилирую сценарии с помощью

ghc --make test.hs -prof 

Что мне не хватает? Бонусный вопрос: я вижу много упоминаний о том, как Lazy IO в Haskell на самом деле плохо. Как/почему я должен использовать строгий IO?

Update:

Так что похоже, что я был неправ в моем чтении Valgrind. Используя +RTS -s, вот что я получаю:

7,807,461,968 bytes allocated in the heap 
1,563,351,416 bytes copied during GC 
     101,888 bytes maximum residency (1150 sample(s)) 
     45,576 bytes maximum slop 
      2 MB total memory in use (0 MB lost due to fragmentation) 

Generation 0: 13739 collections,  0 parallel, 2.91s, 2.95s elapsed 
Generation 1: 1150 collections,  0 parallel, 0.18s, 0.18s elapsed 

INIT time 0.00s ( 0.00s elapsed) 
MUT time 2.07s ( 2.28s elapsed) 
GC time 3.09s ( 3.13s elapsed) 
EXIT time 0.00s ( 0.00s elapsed) 
Total time 5.16s ( 5.41s elapsed) 

Важным направлением является 101,888 bytes maximum residency, который говорит, что в любой данный момент мой сценарий использует 101 кбайт памяти в большинстве. Файл, который я просматривал, составлял 44 мб. Поэтому я считаю, что вердикт: readFile и hGetContents - оба ленивые.

последующий вопрос:

Почему я вижу 7GB памяти выделяется в куче? Это кажется очень высоким для скрипта, который читается в файле размером 44 МБ.

Обновления последующего вопроса

Похоже, несколько гб памяти, выделенная в куче не является нетипичной для Haskell, так что нет причин для беспокойства. Использование ByteString сек вместо String с требуется использование памяти вниз много:

81,617,024 bytes allocated in the heap 
     35,072 bytes copied during GC 
     78,832 bytes maximum residency (1 sample(s)) 
     26,960 bytes maximum slop 
      2 MB total memory in use (0 MB lost due to fragmentation) 
+0

Хмм, вы уверены, что вам не нужно создавать целую строку 'unlines', прежде чем писать ее с помощью' putStrLn'? Я бы попытался что-то вроде «Control.Monad.forM_ (содержимое фильтра (isPrefixOf« импорт ») $ putStrLn'. Однако это предположение. –

+0

@ Riccardo: Нет, 'unlines' можно оценить лениво. Попробуйте 'putStr $ unlines $ map show [1 ..]' в 'ghci'. – ephemient

+0

Делает -O2 волшебно решает проблему? – gspr

ответ

5

Оба readFile и hGetContents должны быть ленивым. Попробуйте запустить вашу программу с помощью +RTS -s и посмотрите, сколько памяти фактически используется. Что заставляет вас думать, что весь файл читается в памяти?

Что касается второй части вашего вопроса, ленивый IO иногда находится в корне неожиданного space leaks или resource leaks. На самом деле это не ошибка ленивого ИО и сама по себе, но определение того, требует ли его утечка анализа того, как он используется.

+0

Да, вы правы :) Любые идеи по моему вопросу о последующих действиях? –

+3

@ VladtheImpala: не беспокойтесь о общей фигуре распределения; это * суммарный объем памяти, выделенный за время жизни программы. Он никогда не уменьшается, даже когда память освобождается сбором мусора, как это часто случается в Haskell; цифры в несколько гигабайт в секунду нередки. – ehird

+0

@ehird ах ладно, спасибо. Я просто не был уверен, что это типично или нет. –

5

Пожалуйста, не используйте обычный String (особенно при обработке> файлов 100 м). Просто замените их ByteString-х (или Data.Text):

{-# LANGUAGE OverloadedStrings #-} 

import Control.Monad 
import System.Environment 
import qualified Data.ByteString.Lazy.Char8 as B 

main = do 
    filename <- liftM getArgs 
    contents <- liftM B.lines $ B.readFile filename 
    B.putStrLn . B.unlines . filter (B.isPrefixOf "import") $ contents 

И я уверен, что это будет в несколько раз быстрее.

UPD: относительно вашего последующего вопроса.
Объем выделенной памяти сильно связан с ускорением магии при переключении на байты.
Как String - это всего лишь общий список, для каждой Char требуется дополнительная память: указатель на следующий элемент, заголовок объекта и т. Д. Вся эта память должна быть выделена, а затем собрана обратно. Это требует большой вычислительной мощности.
С другой стороны, ByteString - это список кусков, то есть непрерывных блоков памяти (я думаю, не менее 64 байт каждый). Это значительно сокращает количество распределений и коллекций, а также улучшает локальность кеша.

+0

Абсолютно согласен на использование ByteStrings ... Я не хотел усложнять ситуацию, добавив это к моему примеру. Но да, это огромная экономия с точки зрения времени и памяти: «81,617,024 байт, выделенных в куче» с «78 832 байтами максимальной резиденции» и «MUT time 0.08s (0,22 s истекло)». –

Смежные вопросы