免责声明:我对 ghc 编译管道知之甚少,但我希望通过这篇文章了解更多信息,例如,比较命令式与函数式是否与代码编译相关。
如您所知,循环展开通过复制其中的代码来减少循环的迭代次数。这提高了性能,因为它减少了跳转次数(以及与之相关的惩罚)和 AFAIR,创建了更大的代码块,为更好的寄存器重命名优化留出了空间。
我想知道,对于函数式编程,是否有等效于循环展开的方法?我们能否“展开”一个函数,打开/扩展它的定义,以首先减少对所述函数的调用次数和/或创建更大的代码块——然后为更多代码重写优化留出空间(如寄存器重命名或一些 FP相等的)?
可以“展开”或“扩展”函数定义的东西,例如使用函数评估(可能与某种策略混合)以便在空间与时间之间进行权衡。
我想到的一个例子:
map1 _ [] = []
map1 f (x:xs) = (f x): map f xs
将展开到
map2 _ [] = []
map2 f (x:x1:xs) = (f x):(f x1):map2 f xs
map2 f (x:xs) = (f x): map2 f xs
再一次:
map4 _ [] = []
map4 f (x:x1:x2:x3:xs) = (f x):(f x1):(f x2):(f x3):map4 f xs
map4 f (x:x1:x2:xs) = (f x):(f x1):(f x2):map4 f xs
map4 f (x:x1:xs) = (f x):(f x1):map4 f xs
map4 f (x:xs) = (f x): map4 f xs
有两件事在起作用:map4 的多个案例(以及列表上的后续测试)可能会降低性能,或者减少 map4 的调用次数会提高性能。也许这可以减少惰性评估造成的一些持续开销?
好吧,编写测试代码似乎并不难,所以在提出标准来推出它之后,这就是我所拥有的:
Problem size 5*10^6
map 105.4 ms
map2 93.34 ms
map4 89.79 ms
Problem size 1*10^7
map 216.3 ms
map2 186.8 ms
map4 180.1 ms
Problem size 5*10^7
map 1050 ms
map2 913.7 ms
map4 899.8 ms
嗯,展开似乎有一些效果^1!map4 似乎快了 16%。
那么问题的时间:
- 以前讨论过这个吗?类似的东西已经实施了吗?
- 真的是减少map4的评估次数提高了速度吗?
- 这可以自动化吗?
- 我可以按块评估吗?即:如果 (fx) 被完全评估,则完全评估 (f x4) 之前的所有内容。
- 这种展开的任何其他形式都在起作用吗?
- 这会导致函数大小的膨胀如何?
- 为什么这不是一个好主意?
1:我也展开了 fib,因为这种优化也会以这种形式发生,但是性能增益是在欺骗一个(非常)糟糕的算法。