GHC优化了分配

时间:2018-01-23 20:22:41

标签: haskell ghc

我正在尝试从this binary-trees benchmark提高The Computer Language Benchmark Game的效果。我们的想法是构建大量二进制树来对内存分配进行基准测试。 Tree数据定义如下所示:

data Tree = Nil | Node !Int !Tree !Tree

根据问题陈述,无需在每个节点和其他语言don't have it中存储Int

我使用GHC 8.2.2并在运行原始代码时获得以下RTS报告:

stack --resolver lts-10.3 --compiler ghc-8.2.2 ghc -- --make -O2 -threaded -rtsopts -funbox-strict-fields -XBangPatterns -fllvm -pgmlo opt-3.9 -pgmlc llc-3.9 binarytrees.hs -o binarytrees.ghc_run
./binarytrees.ghc_run +RTS -N4 -sstderr -K128M -H  -RTS 21
...
      19,551,302,672 bytes allocated in the heap
       7,291,702,272 bytes copied during GC
         255,946,744 bytes maximum residency (18 sample(s))
             233,480 bytes maximum slop
         635 MB total memory in use (0 MB lost due to fragmentation)
...
Total   time   58.620s  ( 39.281s elapsed)

到目前为止一切顺利。让我们删除这个实际上从未使用过的Int。定义变为

data Tree = Nil | Node !Tree !Tree

理论上,我们将节省大约25%的总内存(每个节点中有3个整数而不是4个)。我们来试试吧:

 ...
 313,388,960 bytes allocated in the heap
     640,488 bytes copied during GC
      90,016 bytes maximum residency (2 sample(s))
      57,872 bytes maximum slop
           5 MB total memory in use (0 MB lost due to fragmentation)
 ...
 Total   time    9.596s  (  9.621s elapsed)

使用的总内存为5MB,几乎为零?为什么?所有拨款都去了哪里?

1 个答案:

答案 0 :(得分:3)

我相信Common Sub-expression Elimination优化导致内存使用量突然下降。原始代码是:

make i d = Node i (make d d2) (make d2 d2)
--                      ^           ^
--                      |           d2 != d
--                      d != d2

由于构造左右子树的表达式不同,编译器无法消除任何分配。

如果删除未使用的整数,代码如下所示

make d = Node (make (d - 1)) (make (d - 1))
--            ^              ^
--            |              |
--            `--------------`----- identical

如果我将-fno-cse标志添加到GHC,则内存分配与预期一样高,但代码相当慢。我找不到一种方法来在本地抑制这种优化,所以我决定通过添加额外的未使用的参数来“智取”编译器:

make' :: Int -> Int -> Tree
make' _  0 = Node Nil Nil
make' !n d = Node (make' (n - 1) (d - 1)) (make' (n + 1) (d - 1))

这个技巧奏效了,内存使用量下降了30%。但我希望有一种更好的方式告诉编译器我想要什么。

感谢@Carl提及CSE优化。