简体   繁体   English

GHC优化

[英]GHC optimization

The two Haskell functions below seems to differ only by whether the index variable is implicit or explicit but the difference in performance is by two orders of magnitude. 下面的两个Haskell函数似乎不同,索引变量是隐式还是显式,但性能差异是两个数量级。

This function takes about 0.03 seconds to calculate mfib 30: 此函数大约需要0.03秒来计算mfib 30:

let mfib = (map fib [0..] !!)
  where
    fib 0 = 0
    fib 1 = 1
    fib x = mfib (x-1) + mfib (x-2)

This function takes about 3 seconds for mfib 30: 对于mfib 30,此功能大约需要3秒钟:

let mfib i = map fib [0..] !! i
  where
    fib 0 = 0
    fib 1 = 1
    fib x = mfib (x-1) + mfib (x-2)

I'm guessing it has to do with GHC inline rules and have been trying to add inline/noinline pragmas to get matching performance. 我猜它与GHC内联规则有关,并且一直在尝试添加内联/无内置编译指示以获得匹配的性能。

EDIT: I understand how doing a lookup into the lazy list can be used to memoize the fib function and why the traditional definition of fib is very slow. 编辑:我理解如何查找惰性列表可以用来记忆fib函数以及为什么传统的fib定义非常慢。 I was expecting the memoization to work in the second function as well as the first and don't understand why it is not. 我期待memoization在第二个函数以及第一个函数中工作,并且不理解为什么它不是。

It's easier to understand these differences when looking at the desugared code, so here are partially desugared versions of the two functions. 在查看desugared代码时,更容易理解这些差异,因此这里有两个函数的部分脱落版本。

let mfib = let fib 0 = 0
               fib 1 = 1
               fib x = mfib (x-1) + mfib (x-2)
           in (!!) (map fib [0..])

versus

let mfib = \i ->
               let fib 0 = 0
                   fib 1 = 1
                   fib x = mfib (x-1) + mfib (x-2)
               in map fib [0..] !! i

Note that in the second program, the expression map fib [0..] appears inside \\i -> ... , so it will (normally, without optimizations) evaluated for each value of i . 请注意,在第二个程序中,表达式map fib [0..]出现在\\i -> ... ,因此它(通常,没有优化)将针对i每个值进行评估。 See When is memoization automatic in GHC Haskell? 请参阅GHC Haskell中何时自动记忆?

No, this has nothing to do with inlining. 不,这与内联无关。 The difference is that mfib = (map fib [0..] !!) has no arguments. 区别在于mfib = (map fib [0..] !!)没有参数。 It's still a function of course, but evaluating that function does upfront not require to pass any argument. 它当然仍然是一个函数,但是预先评估该函数不需要传递任何参数。 In particular, evaluating this mfib will generate the fib list in a way that it can be reused for all indices. 特别是,评估此mfib将以一种可以为所有索引重用的方式生成fib列表。

OTOH, mfib i = map fib [0..] !! i OTOH, mfib i = map fib [0..] !! i mfib i = map fib [0..] !! i means the entire where block will only get considered when you actually pass an argument i . mfib i = map fib [0..] !! i意思是当你实际传递一个参数i时,只会考虑整个where块。

The two are only different if you evaluate a function many multiple times again and again. 如果您反复多次评估函数,则两者仅相同。 Unfortunately for the second version, the functions own recursion already calls it again and again! 不幸的是,对于第二个版本,函数自己的递归已经一次又一次地调用它! So mfib (x-1) + mfib (x-2) then needs to do the entire work of mfib (x-1) , and then again the entire work of mfib (x-2) . 所以mfib (x-1) + mfib (x-2)然后需要完成mfib (x-1)的整个工作, 然后再完成mfib (x-2)的全部工作。 So mfib n takes more than twice the computational cost of mfib (n-1) , therefore mfibO (2 n ). 所以mfib n花费两倍以上的计算成本mfib (n-1)因此mfib ∈O(2 N)。

That's incredibly wasteful, because most of the terms in mfib (x-2) are also already in mfib (x-1) and could simply be re-used. 这是非常浪费的,因为mfib (x-2)中的大部分术语也已经在mfib (x-1)并且可以简单地重复使用。 Well, that's exactly what your first version does, because it computes the fib list once and for all indices, so evaluating mfib (x-1) will already do most of the work that can then simply be re-read by mfib (x-2) , reducing the complexity to polynomial. 嗯,这正是你的第一个版本所做的,因为它计算一次和所有索引的fib列表,所以评估mfib (x-1)已经完成了大部分工作,然后可以简单地由mfib (x-2)重新读取mfib (x-2) ,将复杂度降低到多项式。

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

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