Haskell Data.Memocombinators 性能问题?
Posted
技术标签:
【中文标题】Haskell Data.Memocombinators 性能问题?【英文标题】:Haskell Data.Memocombinators performance issues? 【发布时间】:2015-05-19 07:35:40 【问题描述】:_
你好,那里
my program to compute differences between files 的一部分使用标准 DP 算法来计算两个列表之间的最长公共非连续子序列。我在使用其中一些功能时遇到了性能问题,因此我运行 HPC 进行分析,发现以下结果:
individual inherited
COST CENTRE no. entries %time %alloc %time %alloc
(ommitted lines above)
longestCommonSubsequence 1 0.0 0.0 99.9 100.0
longestCommonSubsequence' 8855742 94.5 98.4 99.9 100.0
longestCommonSubsequence'' 8855742 4.2 0.8 5.4 1.6
longestCommonSubsequence''.caseY 3707851 0.6 0.6 0.6 0.6
longestCommonSubsequence''.caseX 3707851 0.6 0.2 0.6 0.2
(ommitted lines below)
这是有问题的代码:
longestCommonSubsequence' :: forall a. (Eq a) => [a] -> [a] -> Int -> Int -> [a]
longestCommonSubsequence' xs ys i j =
(Memo.memo2 Memo.integral Memo.integral (longestCommonSubsequence'' xs ys)) i j
longestCommonSubsequence'' :: forall a. (Eq a) => [a] -> [a] -> Int -> Int -> [a]
longestCommonSubsequence'' [] _ _ _ = []
longestCommonSubsequence'' _ [] _ _ = []
longestCommonSubsequence'' (x:xs) (y:ys) i j =
if x == y
then x : (longestCommonSubsequence' xs ys (i + 1) (j + 1)) -- WLOG
else if (length caseX) > (length caseY)
then caseX
else caseY
where
caseX :: [a]
caseX = longestCommonSubsequence' xs (y:ys) (i + 1) j
caseY :: [a]
caseY = longestCommonSubsequence' (x:xs) ys i (j + 1)
我发现值得注意的是,所有时间和内存使用都发生在 longestCommonSubsequence'
,memoizing 包装器中。因此,我得出的结论是,性能损失来自Data.Memocombinators
完成的所有查找和缓存,尽管在我使用它的许多其他时间里它的性能总是令人钦佩。
我想我的问题是……这个结论似乎是合理的;是吗?如果是这样,那么有人对实现 DP 的其他方法有任何建议吗?
作为参考,将两个 14 行长的文件与各自的内容 "a\nb\nc\n...m"
和 "*a\nb\nc\n...m*"
进行比较需要 12 秒 - 这太长了)。
提前致谢! :)
编辑:现在尝试ghc-core
的东西;如果我能让它与 Cabal 项目很好地配合并获得任何有用的信息,我会发布更新!
【问题讨论】:
你应该发现值得注意的数字是 8855742 和 3707851,这表明你的记忆根本不起作用。 madjar 的回答解释了原因。 【参考方案1】:当您调用Memo.memo2 Memo.integral Memo.integral (longestCommonSubsequence'' xs ys)
时,它会为函数longestCommonSubsequence'' xs ys
创建一个备忘录。这意味着xs
和ys
的每个不同值都有一个记忆器。我猜大部分执行时间都花在为所有这些记忆器创建所有这些数据结构上。
您的意思是要记住longestCommonSubsequence''
的四个参数吗?
【讨论】:
啊,当然!我添加了两个Int
参数以尝试利用Memo.integral
,它应该比Memo.list
更快——如您所见,这两个Int
s 只是被传递并且从未在任何地方使用过。因此,我希望以下表现更好:longestCommonSubsequence' i j xs ys = (Memo.memo2 Memo.integral Memo.integral longestCommonSubsequence'') i j xs ys
因为它总是记住相同的函数,并且仅基于两个整数构建其查找表。但是,我仍然看到非常相似的分析结果。我是不是误会了什么?谢谢! :)
在这里澄清一下;在最初的实现中,它构建了许多表,每个函数都有一个输入,因为柯里化唯一地确定了 i 和 j 将是什么。在上述评论的替代方案中,我希望它只为一个函数(longestCommonSubsequence''
)构建一个表,具有许多不同的输入(i
和 j
的所有值),所以它看起来很奇怪还是那么慢。
嗯;似乎你不能只记住一些论点。对于将来会遇到同样问题的人:我通过将longestCommonSubsequence'
和''
编写为采用Int
s 并声明引用xs
和ys
的二元函数来解决这个问题。范围(请注意,列表索引会很慢,因此您可能希望使用 Data.HashMap 或 Data.Vector 来加快索引速度)!
我正要建议你创建自己的 Memo
,只记住列表大小,但我调查了一下,它似乎比你的解决方案复杂。
嘿嘿!是的——好主意;这是一个链接:hackage.haskell.org/package/filediff-1.0.0.5/docs/src/…以上是关于Haskell Data.Memocombinators 性能问题?的主要内容,如果未能解决你的问题,请参考以下文章
Haskell如何强制在haskell中评估Data.Map?
哪种 KeyBoard / KeyBoard Layout 最适合 Haskell 编程? haskell 是不是将所有数学符号作为函数?