免责声明:我对ghc编译管道知之甚少,但我希望通过这篇文章了解更多信息,例如,如果将命令与功能进行比较与代码编译相关。
如您所知,loop unrolling通过复制其中的代码来减少循环上的迭代次数。这样可以提高性能,因为它减少了跳转次数(以及与之相关的惩罚)和AFAIR,创建了更大的代码块,为更好的Register renaming优化留出了空间。
我想知道,是否可以使用Loop Unrolling进行函数式编程?我们可以展开'一个函数,打开/扩展它的定义,首先减少对所述函数的调用次数和/或创建更大的代码块 - 然后为更多的代码重写优化留出空间(比如寄存器重命名,或者某些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的调用次数会提高性能。也许这可以减少懒惰评估所产生的一些不断开销?
Well that doesn't seems to hard to code a test for,所以在把标准推出后,这就是我所得到的:
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%。
问题的时间:
1:我还展开了fib,因为这种优化也会以这种形式发生,但性能提升会欺骗(非常)糟糕的算法。
答案 0 :(得分:6)
您是否使用优化进行编译?对我而言,使用-O2
,这些片段之间并没有什么区别:map1
,map2
和map4
在279,267和285毫秒中运行,分别(为了比较,map
本身在278ms内运行)。所以这看起来只是测量噪音而不是改进。
那就是说,您可能希望看看this GHC plugin这似乎与循环展开有关。
令人遗憾的是,纯粹的函数式语言和命令式语言往往具有非常不同的优化技术。例如,您可能希望查看流融合和砍伐森林 - 这两种技术非常简洁但不能很好地转换为命令式语言。
至于"为什么这不是一个好主意的任何简短的回应?",好吧,我可以想到一个正确的方法:
*Main> map succ (1:undefined)
[2*** Exception: Prelude.undefined
*Main> map4 succ (1:undefined)
*** Exception: Prelude.undefined
在许多情况下,为了提高性能而使功能更加严格是好的,但是在这里,性能获胜并不是我所理解的,map
通常以懒惰的方式使用。< / p>
答案 1 :(得分:3)
除了已经提到的ghc展开插件外,还有page on the GHC trac讨论剥离/展开。 “公开问题”和“参考文献”部分特别揭示了进一步研究材料的来源。