我一直在玩 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 在幕后做了一些我不明白的事情。
有没有办法在不牺牲其无状态、递归公式(相对于在 ST Monad 中使用可变数组的实现)的情况下使其更高效(最多是 C 程序运行时间的 10-15 倍)?
非常感谢。
编辑:使用的数组模块是标准的 Data.Array