【发布时间】:2012-06-09 23:41:18
【问题描述】:
我一直在玩 Haskell 中的动态编程。实际上,我在该主题上看到的每个教程都提供了相同的、非常优雅的算法,该算法基于记忆化和 Array 类型的惰性。受这些示例的启发,我编写了以下算法作为测试:
-- pascal n returns the nth entry on the main diagonal of pascal's triangle
-- (mod a million for efficiency)
pascal :: Int -> Int
pascal n = p ! (n,n) where
p = listArray ((0,0),(n,n)) [f (i,j) | i <- [0 .. n], j <- [0 .. n]]
f :: (Int,Int) -> Int
f (_,0) = 1
f (0,_) = 1
f (i,j) = (p ! (i, j-1) + p ! (i-1, j)) `mod` 1000000
我唯一的问题是效率。即使使用 GHC 的 -O2,该程序也需要 1.6 秒来计算 pascal 1000,这比等效的未优化 C++ 程序慢了大约 160 倍。并且差距只会随着更大的投入而扩大。
似乎我已经尝试了上述代码的所有可能排列,以及建议的替代方案,如 data-memocombinators 库,它们的性能都相同或更差。我没有尝试过的一件事是 ST Monad,我确信它可以让程序运行的速度只比 C 版本慢一点。但我真的很想用惯用的 Haskell 来写它,我不明白为什么惯用的版本效率如此之低。我有两个问题:
为什么上面的代码效率这么低?这似乎是对矩阵的简单迭代,每个条目都有一个算术运算。显然 Haskell 在幕后做了一些我不理解的事情。
有没有一种方法可以在不牺牲其无状态、递归公式(相对于在圣莫纳德)?
非常感谢。
编辑:使用的数组模块是标准的Data.Array
【问题讨论】:
-
你使用的是哪个数组模块?
-
如果只使用 "f (i,j) = (f (i, j-1) + f (i-1,j))" 并完全放弃 p,性能如何比较?我不明白通过 p 应该如何提供帮助,尽管我承认我对 Haskell 不是很有经验。
-
@DGH:数组的重点是每个结果只计算一次。如果没有数组,算法将是蛮力 - 而不是 DP。
-
我注意到的第一件事是免费的元组,而不是多参数函数。
-
@LouisWasserman 是的,但实际上这没有区别(在我的不科学测量中)。我还没有看过核心,但我希望 GHC 能够优化它们。
标签: performance algorithm haskell functional-programming