避免在Haskell中进行分配

时间:2014-02-16 16:41:59

标签: haskell optimization

我正在研究一个更复杂的程序,我希望它非常有效,但我现在已经把我的问题归结为以下简单的程序:

main :: IO ()
main = print $ foldl (+) 0 [(1::Int)..1000000]

我在这里构建并运行它。

$ uname -s -r -v -m
Linux 3.12.9-x86_64-linode37 #1 SMP Mon Feb 3 10:01:02 EST 2014 x86_64
$ ghc -V
The Glorious Glasgow Haskell Compilation System, version 7.4.1
$ ghc -O -prof --make B.hs
$ ./B +RTS -P
500000500000
$ less B.prof
        Sun Feb 16 16:37 2014 Time and Allocation Profiling Report  (Final)

           B +RTS -P -RTS

        total time  =        0.04 secs   (38 ticks @ 1000 us, 1 processor)
        total alloc =  80,049,792 bytes  (excludes profiling overheads)

COST CENTRE MODULE  %time %alloc  ticks     bytes

CAF         Main    100.0   99.9     38  80000528


                                                      individual     inherited
COST CENTRE MODULE                  no.     entries  %time %alloc   %time %alloc  ticks     bytes

MAIN        MAIN                     44           0    0.0    0.0   100.0  100.0      0     10872
 CAF        Main                     87           0  100.0   99.9   100.0   99.9     38  80000528
 CAF        GHC.IO.Handle.FD         85           0    0.0    0.0     0.0    0.0      0     34672
 CAF        GHC.Conc.Signal          83           0    0.0    0.0     0.0    0.0      0       672
 CAF        GHC.IO.Encoding          76           0    0.0    0.0     0.0    0.0      0      2800
 CAF        GHC.IO.Encoding.Iconv    60           0    0.0    0.0     0.0    0.0      0       248

看起来每次迭代分配80个字节。我认为期望编译器在这里生成无分配代码是非常合理的。

我的期望是否不合理?分配是否是启用分析的副作用?我怎样才能完成任务以摆脱分配?

1 个答案:

答案 0 :(得分:3)

在这种情况下,看起来GHC足够聪明,可以将foldl优化为更严格的形式,但GHC无法优化中间列表,因为foldl不是good consumer ,所以大概是那些分配是针对(:)构造函数的。 EDIT3 :不,看起来不是这样;请参阅注释)

通过使用foldr融合,您可以摆脱中间列表:

main :: IO ()
main = print $ foldr (+) 0 [(1::Int)..1000000]

......正如您所见:

       k +RTS -P -RTS

    total time  =        0.01 secs   (10 ticks @ 1000 us, 1 processor)
    total alloc =      45,144 bytes  (excludes profiling overheads)

具有相同的内存配置文件
main = print $ (1784293664 :: Int)

编辑:在这个新版本中,我们正在为堆栈上的一堆(1 + (2 + (3 +...)))交换堆分配。为了真正得到一个好的循环,我们必须手写,如:

main = print $ add 1000000

add :: Int -> Int
add nMax = go 0 1 where
    go !acc !n
        | n == nMax = acc + n
        | otherwise = go (acc+n) (n+1)

表示:

    total time  =        0.00 secs   (0 ticks @ 1000 us, 1 processor)
    total alloc =      45,144 bytes  (excludes profiling overheads)

EDIT2 我还没有使用Gabriel Gonzalez foldl library,但也可能值得为你的应用程序使用。