【问题标题】:Why Haskell alloc huge amounts of memory when working with strings?为什么 Haskell 在处理字符串时会分配大量内存?
【发布时间】:2012-04-05 21:08:15
【问题描述】:

我用 Haskell 编写了一个程序,它必须加载和解析 UTF8 格式的大文本文件。该文件代表一个字典,每行都有键:值对。在我的程序中,我想要一个用于快速字典搜索的 Data.Map 容器。我的文件大约 40MB,但在将其加载到我的程序后,使用了 1.5 GB 的 RAM,并且从未释放。我做错什么了?内存使用量是预期的吗?

这是我的程序中的代码示例:

模块主要在哪里

import Engine

import Codec.Archive.Zip
import Data.IORef
import System.IO
import System.Directory
import qualified System.IO.UTF8 as UTF8
import qualified Data.ByteString.Lazy as B
import qualified Data.ByteString.UTF8 as BsUtf
import qualified Data.Map as Map

import Graphics.UI.Gtk
import Graphics.UI.Gtk.Glade

maybeRead :: Read a => BsUtf.ByteString -> Maybe a
maybeRead s = case reads $ BsUtf.toString s of
     [(x, "")] -> Just x
     _         -> Nothing    

parseToEntries :: [BsUtf.ByteString] -> [(BsUtf.ByteString, Int)]
parseToEntries [] = []
parseToEntries (x:xs) = let (key, svalue) = BsUtf.break (==':') x
                            value = maybeRead svalue
                        in case value of 
                            Just x -> [(key, x)] ++ parseToEntries xs 
                            Nothing -> parseToEntries xs 

createDict :: BsUtf.ByteString -> IO (Map.Map BsUtf.ByteString Int)
createDict str = do
    let entries = parseToEntries $ BsUtf.lines str
        dict = Map.fromList entries
    return (dict)

main :: IO ()
main = do

    currFileName <- newIORef ""

    dictZipFile <- B.readFile "data.db"    
    extractFilesFromArchive [] $ toArchive dictZipFile
    dictFile <- UTF8.readFile "dict.txt"
    dict <- createDict $ BsUtf.fromString dictFile

...

searchAccent :: Map.Map BsUtf.ByteString Int -> String -> Int
searchAccent dict word = let sword = BsUtf.fromString $ map toLower word
                             entry = Map.lookup sword dict
                         in case entry of
                            Nothing -> -1
                            Just match -> 0                       

【问题讨论】:

  • 我对 Haskell 有点生疏,但是 iirc,++ 语法是内存昂贵的,而 cons 运算符 (:) 很便宜。是否可以使用(key, x) : parseToEntries xs 之类的东西?再次 。 . .我的 Haskell 很生锈,所以这可能是一种方法。
  • @jpm,它的内存开销取决于++ 的第一个参数的长度。在这种情况下,它是不相关的。
  • @maxtaldykin 啊,这很有道理。感谢您的澄清。
  • @jpm,在冈崎书page 9中有关于++如何工作的漂亮图片
  • haskell.org/pipermail/haskell-cafe/2010-August/081772.html 讨论了您正在使用的库的用途,这与您的不同。解压缩一个 23 兆字节的 zip 存档大约需要 4 分钟,没有做任何其他事情,但通过其他方式不到 10 秒。我不认为 (++) 和字符串与字节串是主要的麻烦。

标签: string memory haskell


【解决方案1】:

快速回答。
主要问题是System.IO.UTF8.readFile 将文件读入String

假设的瓶颈在这里:

dictFile <- UTF8.readFile "dict.txt"
dict <- createDict $ BsUtf.fromString dictFile

在处理 UTF-8 文本时,最好使用 Data.Text 而不是 ByteString。 试试这样的:

import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.Encoding as LT

...
dictFile <- B.readFile "dict.txt"
dict <- createDict $ LT.decodeUtf8 dictFile

另一个瓶颈是解析数字:您将ByteString 转换为String,然后将其转换为read。 最好使用Data.Text.Lazy.Read

import qualified Data.Text.Lazy.Read as LT

maybeRead :: LT.Text -> Maybe Int
maybeRead s = case LT.decimal s of
    Left _  -> Nothing
    Right i -> Just i

【讨论】:

  • 谢谢您的回答。 Data.Text 占用的 RAM 内存要少得多。但我使用了我认为更经济的严格版本。
【解决方案2】:

Haskell String 类型是一个间接的(因为懒惰的)字符链表;这是非常在空间方面的浪费。对于大量文本,您不妨试试Data.Text(来自http://hackage.haskell.org/package/text)。

edit 现在源已经启动,我看到字符串是惰性的 ByteString 而不是 String,所以这不相关。分析是下一步。)

【讨论】:

  • 程序只在maybeRead中使用了String,其参数估计比较小。
  • 好的,我看到你现在有部分源代码,主要格式是 UTF8 懒惰ByteString(这并不理想,但比String 好得多)。有可能你太懒了,并且已经建立了 thunk;下一步是使用分析来查看空间的去向。
  • 我不是提问者,但问题从未被编辑过,所以如果添加了来源,它肯定是在前五分钟。 (实际上,我不确定宽限期是否适用于问题......)但是,正如 max 的回答所示,我之前的评论是错误的。
猜你喜欢
  • 2012-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多