Performance 使用无限列表时执行速度较慢
我开始试着了解haskell的表现,以及是什么让事情变得快和慢,我对此有点困惑 我有两个函数的实现,它生成一个素数列表,最大值为某个值。第一个是直接从Haskell wiki上下载的:Performance 使用无限列表时执行速度较慢,performance,haskell,Performance,Haskell,我开始试着了解haskell的表现,以及是什么让事情变得快和慢,我对此有点困惑 我有两个函数的实现,它生成一个素数列表,最大值为某个值。第一个是直接从Haskell wiki上下载的: primesTo :: (Ord a, Num a, Enum a) => a -> [a] primesTo m = eratos [2..m] where eratos [] = [] eratos (p:xs) = p : eratos (xs `minus` [p*p,
primesTo :: (Ord a, Num a, Enum a) => a -> [a]
primesTo m = eratos [2..m] where
eratos [] = []
eratos (p:xs) = p : eratos (xs `minus` [p*p, p*p+p..m])
第二个相同,但在内部使用无限列表:
primes2 :: (Ord a, Num a, Enum a) => a -> [a]
primes2 m = takeWhile (<= m) (eratos [2..]) where
eratos [] = []
eratos (p:xs) = p : eratos (xs `minus` [p*p, p*p+p..])
后一种实现比前一种慢很多(~100倍),我不明白为什么。我原以为哈斯凯尔的懒惰评估会使他们在幕后相当平等
这显然是一个简化的测试用例,用于问题的目的-在现实生活中,优化是没有问题的(尽管我不理解为什么需要优化),但对我来说,只生成无限素数列表的函数比有限素数列表更通用,但处理起来似乎比较慢。在我看来,两者之间有很大的区别
(xs `minus` [p*p, p*p+p..m]) -- primesTo
(xs `minus` [p*p, p*p+p..]) -- primes2
函数减号
成对地遍历列表,并在一个列表到达末尾时终止。在上面的第一个减号表达式中,当后一个列表用尽时,这只在(m-p*p)/p
步骤中发生。在第二种情况下,它将始终按照length xs
的顺序执行步骤
因此,您的无限列表至少禁用了一个有意义的优化。一个区别是,在第二种情况下,您需要生成一个额外的素数。您需要在takeWhile
知道停止时间之前生成大于m
的第一个素数
此外,要筛选的列表和倍数列表上的[…m]
边界有助于减少计算数量。每当这些列表中的一个为空时,减号就会立即通过其secons子句返回,而在无限情况下,减号会在第一种情况下卡住。如果您还测试只有一个列表是无限的情况,则可以更好地探索这一点:
--this is also slow
primes3 :: (Ord a, Num a, Enum a) => a -> [a]
primes3 m = takeWhile (<= m) (eratos [2..m]) where
eratos [] = []
eratos (p:xs) = p : eratos (xs `minus` [p*p, p*p+p..])
--this fast
primes4 :: (Ord a, Num a, Enum a) => a -> [a]
primes4 m = takeWhile (<= m) (eratos [2..]) where
eratos [] = []
eratos (p:xs) = p : eratos (xs `minus` [p*p, p*p+p..m])
——这也很慢
primes3::(Ord a,Num a,Enum a)=>a->[a]
primes3 m=takeWhile(a->[a]
primes4 m=takeWhile(请注意,您可以提高primes2的takeWhile,并获得一个更通用的primes函数。您可以查看生成的Core和C以查看差异,但我认为简单地说,从算法角度考虑,差异在于无限列表版本有一些“重叠”使得到m+1的工作量比primesTo少两倍的工作,对于m,然后对于m+1。但是如果你不需要这项工作,最好不要这样做。因此,对于这个算法,如果你提前知道你将需要的最大m,你可以节省工作量。作为一个练习,找到另一个具有类似属性的问题+算法是很有启发性的。我理解后一个列表终止了减号的执行,当p趋向于m时,减号的计算会变短。我试图理解的是,延迟计算在何处以及何时适用于无限列表-我希望在这种情况下,减号只会被计算为满足takeWhile所需的数量(以及上面的一切)这与显式有界的情况没有太大区别。减号不会成对地通过列表。减号只会在头相等时从两个列表中消耗。因此在这两种情况下,步数都将接近长度xs
。但是,在第一种情况下,您可以耗尽优先级列表mes,避免在p
的最后倍数和下一倍数之间发出砰砰声和检查xs
。
--this is also slow
primes3 :: (Ord a, Num a, Enum a) => a -> [a]
primes3 m = takeWhile (<= m) (eratos [2..m]) where
eratos [] = []
eratos (p:xs) = p : eratos (xs `minus` [p*p, p*p+p..])
--this fast
primes4 :: (Ord a, Num a, Enum a) => a -> [a]
primes4 m = takeWhile (<= m) (eratos [2..]) where
eratos [] = []
eratos (p:xs) = p : eratos (xs `minus` [p*p, p*p+p..m])