简体   繁体   English

带有Integral类型类的Haskell斐波纳契列表

[英]Haskell fibonacci list with Integral typeclass

If I load the following list in GHCi the list is computed slowly, until the program eventually closes just after computing 3524578, the 33rd item in the list. 如果我在GHCi中加载以下列表,则列表计算缓慢,直到程序最终在计算3524578(列表中的第33项)之后关闭。

fibonacci :: (Integral a) => [a]
fibonacci = 0 : 1 : zipWith (+) fibonacci (tail fibonacci)

If I remove the first line and load the following instead the list is computed very quickly and GHCi doesn't close. 如果我删除第一行并加载以下代码,则列表计算速度非常快,GHCi也不会关闭。

fibonacci = 0 : 1 : zipWith (+) fibonacci (tail fibonacci)

Why is the polymorphic list so much slower than the Integer list? 为什么多态列表比Integer列表慢得多? Why does GHCi close? 为什么GHCi关闭?

With your first version, while it looks like a value, you actually write a function (a function on a lower level than Haskell itself). 使用您的第一个版本,它看起来像一个值,您实际上编写了一个函数(一个比Haskell本身更低级别的函数)。

To understand this, think about the following code that would be possible with your definition: 要理解这一点,请考虑以下可能与您的定义相关的代码:

main = println (fibonacci !! 17 :: Int, fibonacci !! 19 :: Integer)

Of course, the 17 and 19 are completly arbitrary. 当然,17和19完全是武断的。 The point is that the first tuple element requires to compute fibonacci as a list of Int , while the second assumes it is a list of Integers . 关键是第一个元组元素需要将fibonacci计算为Int列表,而第二个元素假设它是一个Integers列表。 As you probably know, there is no such list in Haskell that is halfway Int and Integer - a list is either [Int] or [Integer] (or something completely different). 您可能知道,Haskell中没有这样的列表是IntInteger中间 - 列表是[Int][Integer] (或完全不同的东西)。

Because this is so, we can conclude on pure logical grounds, without having deep knowledge of Haskell's run time system, that fibonacci is neither a list of Int nor a list of Integer nor a list of something else - it is just a recipe to build such a list on request. 因为事实如此,我们可以在纯粹的逻辑基础上得出结论,在不深入了解Haskell的运行时系统的情况下, fibonacci 既不Int的列表也不是Integer列表,也不是其他的列表 - 它只是一个构建的方法根据要求提供这样的清单。

That being said, as long as you do something like: 话虽这么说,只要你做的事情如下:

take 10 fibonacci

it should not matter that much (fibonacci is just computed once up to 10 elements). 它应该没那么重要(斐波纳契只计算一次最多10个元素)。

But when you say 但是当你说的时候

map (fibonacci !!) [1..10]

chances are that fibonacci is recomputed for every index. 有可能为每个索引重新计算斐波纳契。 Clearly, the higher the indexes go, the longer this will take. 显然,索引越高,这将花费的时间越长。

This seemed suspicious, so I did a little bit of investigation. 这似乎很可疑,所以我做了一些调查。 First I made two modules: 首先我做了两个模块:

-- fib0.hs
fibonacci :: (Integral a) => [a]
fibonacci = 0 : 1 : zipWith (+) fibonacci (tail fibonacci)
main = print $ take 10000 $ fibonacci

-- fib1.hs
main = print $ take 10000 $ fibonacci
fibonacci = 0 : 1 : zipWith (+) fibonacci (tail fibonacci)

Then compiled as follows: ghc -O2 -prof -auto-all fib0.hs & ghc -O2 -prof -auto-all fib1.hs (Note: & is for Windows, *nix uses ; to seperate multiple commands on a line). 然后编译如下: ghc -O2 -prof -auto-all fib0.hs & ghc -O2 -prof -auto-all fib1.hs (注意: &用于Windows,* nix使用;在一行上分隔多个命令) 。 And ran them with fib0 +RTS -p & fib1 +RTS -p . 并用fib0 +RTS -p & fib1 +RTS -p运行它们。 This will run both fibs and the flags +RTS -p produce a file name fib0/1.prof, which contains runtime information about your program. 这将运行fibs和flags +RTS -p生成文件名fib0 / 1.prof,其中包含有关程序的运行时信息。 Doing this, I saw that they both took the exact same amount of time! 这样做,我看到他们都花了相同的时间! (0.70s to be precise - it may take longer on your machine, if you can't stand waiting that long, then decrease the 10000 elements). (确切地说是0.70秒 - 你的机器可能需要更长的时间,如果你不能等待那么久,那么减少10000个元素)。

You'll notice I compiled with -O2 . 你会注意到我用-O2编译。 This sets optimization to 'level' 2. There are three levels - 0,1, and 2. GHCi by default uses O0 . 这将优化设置为“级别”2.有三个级别 - 0,1和2. GHCi默认使用O0 So then I tried ghc -O0 -prof -auto-all fib0.hs & ghc -O0 -prof -auto-all fib1.hs (ghc would default to -O1 , so you must manually set level 0). 于是我试着ghc -O0 -prof -auto-all fib0.hs & ghc -O0 -prof -auto-all fib1.hs (GHC将默认为-O1 ,所以你必须手动设置0级)。 Wouldn't you know it, when I ran them, fib0 crashes with out of memory exception and fib0 completes just fine (albiet very slowly. I had to decrease take 10000 to take 100 - but this is obviously to be expected). 难道你不知道吗,当我运行它们时,fib0因内存不足而崩溃而且fib0完全正常(albiet非常缓慢。我不得不减少take 10000take 100 - 但这显然是预期的)。

This would be a normal behaviour. 这将是一种正常行为。 Optimization removes bad programming mistakes - like manually enforcing polymorphism when it is beneficial to use a single type - don't do this unless you must! 优化可以消除糟糕的编程错误 - 比如在有利于使用单一类型时手动执行多态 - 除非必须,否则不要这样做!

If you would like to know more, you can try ghc -O0 -f-ext-core fib0.hs & ghc -O0 -f-ext-core fib1.hs . 如果您想了解更多信息,可以试试ghc -O0 -f-ext-core fib0.hs & ghc -O0 -f-ext-core fib1.hs This will generate 'core' files for both programs. 这将为两个程序生成“核心”文件。 Core is the last stage of GHC compilation before you start making object files. 在开始制作目标文件之前,Core是GHC编译的最后一个阶段。 It generates plain-text .hcr files. 它生成纯文本.hcr文件。 These may be very hard to read, so let me highlight some key points for you. 这些可能很难阅读,所以让我强调一些关键点。

fib0.hcr contains the following: fib0.hcr包含以下内容:

...
base:GHCziNum.fromInteger @ ac zddNumazzzz
...

Basically, you call fromInteger on every single number, starting with 0 and 1, in your sequence. 基本上,您可以在序列中的每个数字上调用fromInteger ,从0和1开始。 Why do this? 为什么这样? You have told it, "the type of fibonacci must be any numeric type". 你告诉它,“斐波纳契的类型必须是任何数字类型”。 It creates 0 and 1 as Integer s and then uses fromInteger to create values of type Num a => a (This is the only way to create polymorphic literals) . 它创建0和1作为Integer ,然后使用fromInteger创建类型为Num a => a (这是创建多态文字的唯一方法)。 All of these calls to fromInteger build up very expensive thunks - so you get out of memory exception. 所有这些对fromInteger调用都会构建非常昂贵的thunk - 所以你会遇到内存异常。

Take a look at fib1.hcr. 看一下fib1.hcr。 Nowhere does it contain fromInteger . 它没有包含fromInteger Leaving GHC to infer the type of fibonacci lets it just use Integer , which means no thunks. 让GHC推断出斐波纳契的类型让它只使用Integer ,这意味着没有thunks。

Why does optimizing remove this problem? 为什么优化会消除此问题? GHC notices that you only use fibonacci to print the numbers. GHC注意到您只使用斐波纳契来打印数字。 You don't need this polymorphism. 您不需要这种多态性。 So it optimizes it away! 所以它优化了它! Note that it also does other things that make fibonacci much, much faster, like inlining. 请注意,它还可以做其他使fibonacci更快,更快的内容,如内联。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM