Performance 使用Haskell查找性能问题LCS

Performance 使用Haskell查找性能问题LCS,performance,haskell,lcs,Performance,Haskell,Lcs,这是一个经典的编程问题 JS实现通过了所有测试,但Haskell实现消耗了太多内存并被杀死 我做错了什么 ——从上到下 commonChild s1 s2=L.foldl g l1 L!N 哪里 n=长度s1 l1=arr$复制(n+1)0 l=[[(x,i,y,j)|(y,j)使用数据中的/。数组确实在扼杀性能。如果您阅读,它会说“构造一个与第一个参数相同的数组,只是它已由右参数中的关联更新”,这意味着每次调用它时,都在构造一个全新的数组。这与js实现非常不同,后者只是附加 你可能认为数组是获

这是一个经典的编程问题

JS实现通过了所有测试,但Haskell实现消耗了太多内存并被杀死

我做错了什么

——从上到下
commonChild s1 s2=L.foldl g l1 L!N
哪里
n=长度s1
l1=arr$复制(n+1)0

l=[[(x,i,y,j)|(y,j)使用
数据中的
/
。数组
确实在扼杀性能。如果您阅读,它会说“构造一个与第一个参数相同的数组,只是它已由右参数中的关联更新”,这意味着每次调用它时,都在构造一个全新的数组。这与js实现非常不同,后者只是附加

你可能认为数组是获得性能提升的明显选择,但这是一个常规的旧列表会做得很好的时候,而不是在折叠的每一次迭代中生成一个新的数组,每个元素都有一个新的元素在前面,你可以把它加入到一个列表中。功能

g

ga=arr.reverse.L.foldl(内部a)[0]
内部a'@(z:uz)(x,i,y,j)=
设x'=如果x==y
然后1+a!(j-1)
else max(a!j)z
在x中:a'


注意:我上面所做的更改都是为了选择更好的数据结构,但更多提高性能的方法请参见@chi的答案,这些方法与协商惰性/严格性和做GHC特定的事情有关。

我通过

  • 添加类型签名
  • 使用
    foldl'
  • 使用爆炸模式强制严格
  • 使用
    -O2
    编译(避免GHCi)
下面是修改后的代码(删除了长测试字符串):


(或者干脆放弃多态性,使用
String->String->Int
)。

阅读Wikipedia对该算法的描述,我直接找到了一个只使用列表而不使用数组的实现:

{-#语言模式}
--从字符沿行方向计算下一行
--左边缘、沿上边缘的字符串和上一个
--划船。
makeRow::Char->String->[Int]->[Int]
makeRow匹配=go 0
哪里
--第一个参数是参数中的值
--到当前的左上角和紧邻的左下角
--牢房。
转到::Int->Int->Char->String->[Int]->[Int]
上!左上!左(c:cs)(l:ls)=
cur:go l cur cs ls
哪里
!
|c==匹配=1+左上
|否则=左最大l
go.[]
commonChild s1=go(重复(0::Int))
哪里
go ls[]=最后一个ls
go ls(c:cs)=go(makeRow c s1 ls)cs
这足够快,可以通过所有测试,而且比使用数组要简单得多。常数因子可以通过各种方式进行改进,但这是一个很好的起点。我尝试改进这一点的第一个方法是使用如下类型替换
[Int]

data IntList = Cons !Int IntList | Nil

这为每个元素节省了两个字的内存和一个指针的间接寻址。切换到非固定数组(至少对于
Int
列表)在许多情况下应该会有进一步的改进,但这会非常烦人。

“消耗太多内存”=>可能太懒了。我的第一个猜测是“将
foldl
替换为
foldl'
”…@MathematicalOrchid谢谢!但这没有帮助…你应该提供一个完整的示例,可以剪切粘贴、编译并观察缓慢的性能(比如至少几秒钟的计算)。我花了几分钟进行编译,并添加了你的测试
lcs(“SHINCHAN”,“NOHARAAA”)
,但它只运行了几毫秒,所以我看不出问题出在哪里,所以我停止了。@chi我感谢您的时间)在这里您可以找到示例,我还创建了一个repl Brilliant!它速度快得多,但仍然比js慢,并通过了一些性能测试。但是它没有被杀死,并返回了正确的结果。谢谢。最明显的是我最初的改进是以这样或那样的方式停止使用
reverse
。这通常是一个性能杀手。接下来我可能会取消这些数组的装箱。但我也很好奇算法是否可以改进。@dfeur是的,我试图用“从下到上”来实现函数"避免逆转的方法,目前为止运气不佳。我在维基百科的文章中添加了一个关于LCSY的链接。你至少可以通过编写一个多态工作程序,让一个单态包装器使用
runST
来延迟放弃多态性。@chi谢谢!我必须了解更多有关LCSY的信息STUArray@chi非常感谢!您的改进有帮助,现在功能正常了通过了所有测试!事实证明,完全跳过数组而只使用列表是非常容易的。
commonChild ::
   ( Eq a
   , forall s. MArray (STUArray s) a (ST s)    -- requires some extension
   ) => [a] -> [a] -> Int
data IntList = Cons !Int IntList | Nil