2012-03-17 134 views
9

假設我有幾個200mb +的文件需要grep。我如何在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 

我錯過了什麼?獎金問題:我看到很多關於如何在Haskell中使用惰性IO實際上是一件壞事的提及。如何/爲什麼我會使用嚴格的IO?

更新:

所以看起來我錯了,我的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 KB最多。我掠過的文件是44 MB。所以我認爲判決是:readFilehGetContents都是懶惰的。

後續問題:

爲什麼我看到的內存7GB在堆上分配?對於在44 MB文件中讀取的腳本,這看起來非常高。

更新後續問題

貌似的堆上分配的內存數GB的不是非典型哈斯克爾,關注所以沒有原因。使用替代String小號ByteString S可內存使用率下降了不少:

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

哼,你確定在用'putStrLn'實際編寫之前不需要建立整個'unlines'字符串嗎?我會嘗試像'Control.Monad.forM_(過濾器(isPrefixOf「導入」)內容)$ putStrLn'。然而,這只是一個猜測。 – 2012-03-17 01:19:32

+0

@Riccardo:不,可以懶惰評估'unlines'。在'ghci'中試試'putStr $ unlines $ map show [1 ..]'。 – ephemient 2012-03-17 01:28:20

+0

-O2神奇地解決了這個問題? – gspr 2012-03-17 07:55:13

回答

5

兩個readFilehGetContents應該是懶惰。嘗試使用+RTS -s運行程序並查看實際使用的內存量。是什麼讓你認爲整個文件被讀入內存?

至於你的問題的第二部分,惰性IO有時是意外的space leaksresource leaks的根源。不是真正的惰性IO本身的錯誤,而是確定它是否泄漏需要分析它的使用方式。

+0

是的,你是正確的:)我的後續問題的任何想法? – 2012-03-17 05:16:24

+3

@VladtheImpala:不要擔心總分配數字;它是在程序生命週期內分配的*總量*內存量。即使內存被垃圾收集釋放,它也不會減少,就像Haskell經常發生的那樣;每秒數千兆字節的數字並不少見。 – ehird 2012-03-17 05:57:13

+0

@ehird啊好吧,謝謝。我只是不確定這是否是典型的。 – 2012-03-17 20:15:03

5

請不要使用普通的String's(尤其是當您處理大於100m的文件時)。 只是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時間爲0.08秒(已過時0.22秒)。 – 2012-03-17 20:14:13