Предположим, у меня есть несколько файлов размером более 200 МБ, которые я хочу просмотреть. Как бы я сделал это в 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
ленивый, он избежит чтения всего файла в память.Но запуск обоих скриптов под valgrind
показал одинаковое использование памяти для обоих. Так что либо мой сценарий неверен, либо valgrind
неверен. Я компилирую скрипты, используя
ghc --make test.hs -prof
Что мне не хватает? Бонусный вопрос: я вижу много упоминаний на SO о том, что отложенный ввод-вывод в Haskell на самом деле плох. Как/зачем мне использовать строгий ввод-вывод?
Обновление:
Похоже, я ошибся в своем прочтении 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 байт максимального резидентства
, в которой говорится, что в любой момент мой скрипт использовал 101 КБ памяти. в большинстве. Файл, который я искал, весил 44 МБ. Поэтому я думаю, что вердикт таков: readFile
и hGetContents
оба ленивы.
Дополнительный вопрос:
Почему в куче выделено 7 ГБ памяти? Это кажется очень высоким для сценария, который читает файл размером 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)