【问题标题】:TemplateHaskell memory usage when compilingTemplateHaskell 编译时的内存使用情况
【发布时间】:2015-09-10 19:30:36
【问题描述】:

在我的 Haskell 项目之一 RuzzSolver 中使用 TemplateHaskell 时,我遇到了内存消耗问题。 Sources of RuzzSolver are available on GitHub .

为了获得良好的性能,我将约 380000 个单词的字典加载到 Tree 结构中(来自容器包)。这极大地加快了网格的求解速度,但加载本身需要一些时间(取决于 CPU,在 1 到 2 秒之间)。

我想在编译时使用 TemplateHaskell 直接创建结构。

因此我转换了字典加载:

-- Dictionary.hs, line 155
getDictionary :: String -> IO Dictionary
getDictionary dictionaryFilePath = do
    content <- readFile dictionaryFilePath
    return $ foldl (+++) [] (createTree <$> lines content)

进入这个函数:

-- Dictionary.hs, line 164
getDictionaryQ :: String -> Q Exp
getDictionaryQ dictionaryFilePath = do
    content <- runIO $ readFile dictionaryFilePath
    lift $ foldl (+++) [] (createTree <$> lines content)

view Dictionary.hs

它允许我从:

-- ruzzSolver.hs, line 68
dictionary <- getDictionary "dictionary/ruzzdictionary.txt"

到:

-- ruzzSolver.hs, line 68
let dictionary = $(getDictionaryQ "dictionary/ruzzdictionary.txt")

view ruzzSolver.hs

它(应该)可以工作,但是编译需要太多内存!在我的 8 Gb PC 上,当 GHC 消耗到 12 GB 时,我不得不停止它。将字典减少到 38000 个单词可以编译,但仍需要 3 到 4 GB。

在编译这个 TemplateHaskell 代码时,有没有办法让 GHC 使用更少的内存?或者以其他方式将此结构嵌入到可执行文件中?

【问题讨论】:

  • 这与您的问题没有直接关系,但听起来您将树用作某种特里树。有一些图书馆提供尝试,包括bytestring-trieword-trie
  • @dfeuer 你是对的,这是一个尝试:-)
  • 似乎加载几个 meg 文件应该没什么大不了的。仔细查看您的加载代码。您是否使用(懒惰的)bytestrings 来加载文件?我希望文件已经排序?你能想出一个文件表示,它将树存储在一个只需要很少计算加载的状态吗?例如使用Data.Binary?疯狂的想法:基于mmap实现一个trie。
  • 显然,如果您正在预计算一个巨大的 AST,您应该关闭该模块的优化。
  • @luqui 是的,这是一个巨大的 AST ;-) 文件已经排序。我不使用(惰性)字节串,我使用标准的 readFile。我曾经尝试使用 Text 函数来加载文件,但没有任何区别。

标签: haskell template-haskell


【解决方案1】:

也许您可以将 trie“嵌入”到可执行文件中以节省加载和创建时间,但我预见到的一个问题是,与其他语言的数据结构相比,传统的 Haskell 数据结构相当臃肿。

此外,大多数容器都允许插入和删除,但看起来您的数据是不变的,因此您只需要最终的数据结构。此外,您只会将其用于以下查询:

  • 字典中是否存在这个词?
  • 而且,这个字符串是字典中某个单词的前缀吗?

您希望使用某种预先计算的索引来紧凑地表示字典以加快查找速度。

一些选项:

选项 1:创建 BerkeleyDB 数据库。

这样的数据库允许大于和小于查询。

优点:没有数据库加载时间。

缺点:查询需要磁盘访问。虽然,一旦页面被操作系统读取,它们应该被缓存并且后续读取应该很快。

注意 - 我使用 Berkeley DB 在 perl 中编写了一个 boggle 求解器,因此这种方法非常可行。

与 BerkeleyDB 类似的是 CDB(常量数据库),它也有一个 Haskell 包。但是,CDB 仅支持相等查询,因此它可能不适用于您的应用程序。

选项 2. 将字典简单地表示为单词的排序文件。创建自定义索引以提高查询效率。

一个简单的索引可能只是一个 26*26*26 元素数组,表示每个三字母前缀在文件中的偏移量。这么小的索引可以编译进程序。将字典加载为单个(严格)ByteString。

使用字节字符串中的索引和二进制搜索来解决查询。 也许 ByteString 函数在这里可以很好地工作,但作为最后的手段,您始终可以使用加载字典中的 Int 偏移量作为“指针”,您可以移动它来查找下一个单词的开头。

您也许可以将字典 ByteString 编译到可执行文件中,但加载 4 MB 数据不会花费太长时间 - 特别是如果它已经在操作系统缓存中。

更新:可以在here 找到第二个想法的示例。

【讨论】:

  • 虽然没有解决 TemplateHaskell 的内存问题,但是帮了大忙,谢谢! :-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-05
  • 1970-01-01
  • 1970-01-01
  • 2019-03-14
  • 1970-01-01
  • 2010-09-12
相关资源
最近更新 更多