在第一次看到Haskell四年之后回到Haskell。我总是对表现力感到惊讶,并且因为我无法预测空间/时间表现而感到困惑。
作为一个热身,我开始翻译我用C ++编写的一个小玩具程序。关于"作弊"在Scrabble。你输入你的游戏,它会输出你可能会玩的单词,单独用你的信件或者在棋盘上写一个字母。
整个事情围绕着一个在启动时预装的词典。然后将这些单词与其字谜一起存储为地图中的列表。键是排序字母的字符串。一个例子会说得更清楚:
Key : "AEHPS" Value : ["HEAPS","PHASE","SHAPE"]
C ++版本一次一个地读取字典的~320000个单词,总共大约200ms。生成的数据结构是存储在array<99991, vector<string>>
中的哈希映射,占用大约12兆字节的内存。
Haskell版本在大约5秒内读取相同的字典,程序堆大小膨胀到400兆字节!我将Data.Map
中的值类型从[String]
更改为[ByteString]
以节省一些内存,这使程序内存消耗降低到大约290兆字节。这仍然是我的C ++版本的24倍。这不仅仅是开销&#34;,即使Data.Map
是树而不是数组。
所以我认为我做错了什么。
整个模块在这里可见:(不建议使用的链接)
我认为我的麻烦与Data.Map
逐步建立的方式有关,在以前的版本中增长?或者与数据结构本身?或其他什么?
我会尝试其他解决方案,例如Data.HashMap
,或用Data.Map
填写fromListWith
。不过,我想建立一些对这里发生的事情的理解。非常感谢您的任何见解!
简短回答:
使用Data.Map.Strict,强制评估值元素,并将键存储为ByteStrings也使得将内存占用量分成近乎3的奇迹。结果是100Meg,这只是标准的两倍{ {1}}在C ++中用于相同的数据集。虽然没有加速。 Git已更新。
非常感谢所有贡献者,这里有一些有趣的内容!
答案 0 :(得分:7)
您尚未指出的一个错误是您将B.pack word
形式的未评估的thunk存储在地图中的值列表中。这意味着在构建Map期间,您将以低效的String格式保留整个输入文件,输入文件中每个字符的成本为24字节。使用Data.Map.Strict
API在这里没有区别,因为该API中的函数仅强制Map的元素为弱头正常形式,对于列表而言仅表示仅评估最外层构造函数是[]
还是(:)
,而不是评估任何列表的元素。
您可以做的另一项改进是使用最新版本的字符串中可用的ShortByteString
类型(GHC 7.8附带的那个是足够新的)。这是专为在存储许多短字节串时最小化内存使用而设计的,权衡是ShortByteString
上的大多数操作都需要副本。
AndrásKovács的Map示例代码看起来像这样:
{-# LANGUAGE BangPatterns #-}
import Control.Applicative
import Data.List
import qualified Data.Map.Strict as M
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Short as B (ShortByteString, toShort, fromShort)
shortPack = B.toShort . B.pack
main = do
words <- lines <$> readFile "dict.txt"
print $ M.size $
M.fromListWith (++) $ map (\w -> let !x = shortPack w in (shortPack $ sort w, [x])) words
这些更改中的每一项都可以节省我测试中最大驻留时间的30%,总节省超过50%的空间。
答案 1 :(得分:6)
编辑:删除了错误的基准测试和评论,只留下了一些建议。请参阅Reid Barton关于直接解决OP问题的答案。
如果您不需要在运行时更改字典,那么DAWG-s几乎是您可以获得的最具时空效率的解决方案(至少对于文字游戏而言)。
例如,我们可以从您的字典中生成并序列化DAWG,它只占用295 Kb空间,并支持非常有效的查找和前缀匹配:
import qualified Data.DAWG.Packed as D -- from my "packed-dawg" package
main = do
words <- lines <$> readFile "dict.txt"
D.toFile "dict.dawg" $ D.fromList words -- serialize as "dict.dawg"
答案 2 :(得分:2)
以下内容适用于我的笔记本电脑:
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Data.List
type Dict = M.Map T.Text [T.Text]
newDict = M.empty
addWord:: T.Text -> Dict -> Dict
addWord word dict = M.insertWith (++) (T.pack $ sort $ T.unpack word) [word] dict
loadAnagramsFromFile fileName = do
full <- T.readFile fileName
let ls = T.lines full
return $ foldr addWord newDict lsct ls
这是使用Text,只有一个wart进行排序。可能有更好的方法来排序文本。