首先,如果您要对数字内容进行性能比较,列表不是最佳选择。尝试一个包,比如用于快速数组的vector包。
请注意,由于循环融合,您可以在 Haskell 中做得更好。通过将创建函数编写为枚举,编译器可以将创建步骤和折叠循环组合成一个不分配中间数据结构的循环。像这样进行一般融合的能力是 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# -> [Double] -> Double#
$wlgo =
\ (ww_sww :: Double#) (w_swy :: [Double]) ->
case w_swy of _ {
[] -> ww_sww;
: x_aoY xs_aoZ ->
case x_aoY of _ { D# x1_aql ->
$wlgo
(+##
ww_sww (*## x1_aql (sqrtDouble# x1_aql)))
xs_aoZ
}
}
$wcreate :: Double# -> [Double]
$wcreate =
\ (ww_swp :: Double#) ->
case ==## ww_swp 0.0 of _ {
False ->
:
@ Double
(D# ww_swp)
($wcreate (-## ww_swp 1.0));
True -> [] @ Double
}
请注意如何有两个循环:创建生成(惰性)列表,以及使用它的折叠。由于懒惰,该列表的成本很便宜,因此它以可观的方式运行:
$ 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
这是纯度保证提供的强大功能的一个很好的例子——编译器可以非常积极地重新排列你的代码。