如果你正在进行数值比较的性能对比,首先要注意列表不是最佳选择。尝试使用类似vector这样的包来获得快速数组。
还要注意,在Haskell中,由于循环融合(loop fusion)的特性,你可以做得更好。通过将创建函数编写为枚举方式,编译器可以将创建步骤和折叠循环合并为一个单独的循环,从而不会分配任何中间数据结构。像这样进行通用融合的能力是GHC Haskell所特有的。
我将使用向量库(基于流的循环融合):
import qualified Data.Vector as V
test = V.foldl (\ a b -> a + b * sqrt b) 0
create n = (V.enumFromTo 1 n)
main = print (test (create 1000000))
现在,在你的代码之前,编译器无法移除所有列表,我们最终会得到一个类似于以下内部循环:
$wlgo :: Double
$wlgo =
\ (ww_sww :: Double
case w_swy of _ {
[] -> ww_sww;
: x_aoY xs_aoZ ->
case x_aoY of _ { D
$wlgo
(+
ww_sww (*
xs_aoZ
}
}
$wcreate :: Double
$wcreate =
\ (ww_swp :: Double
case ==
False ->
:
@ Double
(D
($wcreate (-
True -> [] @ Double
}
请注意有两个循环:create生成一个(惰性)列表,而fold则消耗它。由于惰性,该列表的成本很低,因此其运行速度相当快:
$ time ./C
4.000004999999896e14
./C 0.06s user 0.00s system 98% cpu 0.058 total
然而,在融合下,我们得到的是一个单独的循环!
main_$s$wfoldlM_loop :: Double# -> Double# -> Double#
main_$s$wfoldlM_loop =
\ (sc_sYc :: Double#) (sc1_sYd :: Double#) ->
case <=## sc_sYc 1000000.5 of _ {
False -> sc1_sYd
True ->
main_$s$wfoldlM_loop
(+## sc_sYc 1.0)
(+##
sc1_sYd (*## sc_sYc (sqrtDouble# sc_sYc)))
GHC将我们的创建和测试步骤缩减为一个循环,不使用任何列表。只有2个双精度浮点数在寄存器中。由于循环次数减半,运行速度快了近两倍:
$ ghc D.hs -Odph -fvia-C -optc-O3 -optc-march=native -fexcess-precision --make
$ time ./D
4.000005000001039e14
./D 0.04s user 0.00s system 95% cpu 0.038 total
这是一个很好的例子,展示了纯度保证所提供的强大功能--编译器可以非常激进地重新排列您的代码。
lazy
和Lazy.force
,这是任何人都可以通过引用闭包来编写的明显的ML端实现。在3.0中,特殊支持已集成到运行时中,并在3.11.0中增加了更多支持(模式匹配)。如今,GC会在挂起已被强制执行之后一段时间内删除间接性。http://ralyx.inria.fr/2008/Raweb/gallium/uid48.html - Pascal Cuoq