这看起来很可疑,所以我做了一些调查。首先我做了两个模块:
-- 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)
然后编译如下:(ghc -O2 -prof -auto-all fib0.hs & ghc -O2 -prof -auto-all fib1.hs
注:&
适用于Windows,*nix用于;
分隔一行中的多个命令)。并用fib0 +RTS -p & fib1 +RTS -p
. 这将同时运行 fibs,并且标志+RTS -p
会生成一个文件名 fib0/1.prof,其中包含有关您的程序的运行时信息。这样做,我看到他们都花费了完全相同的时间!(准确地说是 0.70 秒 - 你的机器上可能需要更长时间,如果你不能忍受等待那么久,那么减少 10000 个元素)。
你会注意到我用-O2
. 这会将优化设置为“级别”2。共有三个级别 - 0、1 和 2。GHCi 默认使用O0
. 所以然后我尝试ghc -O0 -prof -auto-all fib0.hs & ghc -O0 -prof -auto-all fib1.hs
了(ghc 将默认为-O1
,因此您必须手动设置级别 0)。难道你不知道吗,当我运行它们时,fib0 崩溃并出现内存不足异常,并且 fib0 完成得很好(虽然非常缓慢。我不得不减少take 10000
到take 100
- 但这显然是意料之中的)。
这将是一种正常的行为。优化消除了糟糕的编程错误——比如在使用单一类型有益时手动强制执行多态性——除非你必须这样做,否则不要这样做!
如果你想了解更多,可以试试ghc -O0 -f-ext-core fib0.hs & ghc -O0 -f-ext-core fib1.hs
。这将为两个程序生成“核心”文件。核心是开始制作目标文件之前 GHC 编译的最后一个阶段。它生成纯文本 .hcr 文件。这些可能很难阅读,所以让我为您强调一些要点。
fib0.hcr 包含以下内容:
...
base:GHCziNum.fromInteger @ ac zddNumazzzz
...
基本上,您调用fromInteger
序列中的每个数字,从 0 和 1 开始。为什么要这样做?您已经告诉它,“斐波那契的类型必须是任何数字类型”。它将 0 和 1 创建为Integer
s,然后用于fromInteger
创建类型的值Num a => a
(这是创建多态文字的唯一方法)。所有这些调用都会fromInteger
建立非常昂贵的 thunk - 所以你会出现内存不足异常。
看看 fib1.hcr。它不包含任何地方fromInteger
。让 GHC 来推断斐波那契的类型让它只使用Integer
,这意味着没有 thunk。
为什么优化会消除这个问题?GHC 注意到您只使用斐波那契来打印数字。你不需要这种多态性。所以它优化了它!请注意,它还执行其他使斐波那契变得更快的事情,例如内联。