haskell - 避免 Haskell 中的分配

标签 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 个字节。我认为期望编译器在这里生成免分配代码是相当合理的。

我的期望不合理吗?分配是启用分析的副作用吗?我怎样才能摆脱分配?

最佳答案

在这种情况下,看起来 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但它也可能值得您的应用程序使用。

关于haskell - 避免 Haskell 中的分配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21814165/

相关文章:

haskell - 与中缀一起使用时,约束错误中的非类型变量参数

haskell - 是否有无符号整数类型会警告负文字?

C++ : NLopt COBYLA as compared to Matlab fmincon

mysql - 在 MySql 中一次获取 15 个表结果的更好方法

c++ - 优化 C++11 随机生成器的使用

haskell - 如何在 Haskell 中编写类似 Linux Top 的控制台程序?

ruby - Haskell 通过 FFI 与 Ruby 绑定(bind)?

haskell - 使用 Parsec 解析配置

optimization - 这些分配从哪里来?声明参数的类型如何阻止它们?

c - 为什么 int * float 比 int/int 快?