Haskell:无法理解瓶颈

时间:2014-07-16 06:51:06

标签: haskell list-comprehension

我解决了项目Euler问题,然后用Haskell维基上的问题面对我的解决方案。他们非常相似,但是我的时间是7.5秒,而另外0.6!我把它们都编成了。

我看起来如下:

main = print . maximumBy (compare `on` cycleLength) $ [1..999]
        where cycleLength d = remainders d 10 []

和其中一个维基:

main = print . fst $ maximumBy (comparing snd) [(n, cycleLength n) | n <- [1..999]]
        where cycleLength d = remainders d 10 []

我还尝试使用compare `on`更改comparing cycleLength,但效果保持不变 因此,我必须得出结论,所有差异都在于计算运行中的值与在列表理解中进行转换。

但时间上的差异非常大:第二个版本的加速速度提高了12.5倍!

1 个答案:

答案 0 :(得分:11)

maximumBy函数会多次重复检查列表中的相同数字 - 每次检查一个数字时,都必须重新计算 cycleLength。这是一项昂贵的操作!

wiki算法因此使用称为decorate-sort-undecorate的技术。现在,在这里你没有排序,但它足够接近。您首先预先计算所有数字的cycleLength值(即您创建'缓存')然后执行最大操作,然后然后您将其解包(使用fst。)这样,你可以省去很多计算!

编辑:要说明一下,请查看maximumBy来源中的Data.List函数:

-- | The 'maximumBy' function takes a comparison function and a list
-- and returns the greatest element of the list by the comparison function.
-- The list must be finite and non-empty.
maximumBy               :: (a -> a -> Ordering) -> [a] -> a
maximumBy _ []          =  error "List.maximumBy: empty list"
maximumBy cmp xs        =  foldl1 maxBy xs
                        where
                           maxBy x y = case cmp x y of
                                      GT -> x
                                      _  -> y

它在2的窗口中移动;请求每个号码(在您的情况下计算)两次。 这意味着对于999次迭代,您的版本将调用cycleLength d 1996次(n * 2-2),而维基版本将调用它999(n)次。

这并不能解释完全延迟 - 只有2倍,但因子更接近于10。

以下是您的版本的个人资料,

COST CENTRE entries %time %alloc %time %alloc MAIN 0 0.0 0.0 100.0 100.0 CAF 0 0.0 0.0 100.0 100.0 main 1 0.0 0.0 100.0 100.0 f 1 0.0 0.0 100.0 100.0 maximumBy 1 0.0 0.0 100.0 99.9 maximumBy.maxBy 998 0.0 0.1 100.0 99.9 cycleLength 1996 0.1 0.2 100.0 99.8 remainders 581323 99.3 94.4 99.9 99.7 remainders.r' 581294 0.7 5.2 0.7 5.2

和wiki版本:

COST CENTRE entries %time %alloc %time %alloc MAIN 0 0.0 0.0 100.0 100.0 CAF 0 0.0 0.0 100.0 99.9 main 1 0.0 0.1 100.0 99.9 f' 1 0.0 0.8 100.0 99.8 cycleLength 999 0.2 0.5 100.0 98.6 remainders 95845 98.3 93.0 99.8 98.2 remainders.r' 95817 1.5 5.2 1.5 5.2 maximumBy 1 0.0 0.1 0.0 0.4 maximumBy.maxBy 998 0.0 0.2 0.0 0.2

查看此处的配置文件,您的版本似乎经历了更多的分配(大约10到12倍),但总体上没有使用更多的RAM。因此,我们需要根据分配来解释累积因子5或6。

剩余是递归的。在您的示例中,它被称为581294次。在wiki示例中,它被调用了95817次。我们增加了5-6倍!

所以我认为这里的compare电话也是一个问题。因为它将cycleLength应用于我们想要比较的两件事情!在wiki问题中,cycleLength会应用于每个数字,但是在这里,我们将它应用于每个数字两次,比较似乎更频繁地应用,这是一个问题特别是数字越大,因为remainders的复杂性很差(似乎是指数级的,但我不确定。)

由于两个程序的最大内存消耗并没有太大的不同,我认为这与堆没有任何关系。