19

可能重复:
用 GHC 编译成巨大二进制文件的小型 Haskell 程序

最近我注意到 Haskell 可执行文件有多大。下面的所有内容都是在 GHC 7.4.1 和-O2Linux 上编译的。

  1. Hello World ( main = putStrLn "Hello World!") 超过 800 KiB。运行strip它会将文件大小减少到 500 KiB;即使添加-dynamic到编译中也无济于事,给我留下了大约 400 KiB 的剥离可执行文件。

  2. 编译一个涉及 Parsec 的非常原始的示例会生成一个 1.7 MiB 的文件。

    -- File: test.hs
    import qualified Text.ParserCombinators.Parsec as P
    import Data.Either (either)
    
    -- Parses a string of type "x y" to the tuple (x,y).
    testParser :: P.Parser (Char, Char)
    testParser = do
        a <- P.anyChar
        P.char ' '
        b <- P.anyChar
        return (a, b)
    
    -- Parse, print result.
    str = "1 2"
    main = print $ either (error . show) id . P.parse    testParser "" $ str
    -- Output: ('1','2')
    

    Parsec 可能是一个更大的库,但我只使用了它的一小部分,实际上由上面生成的优化核心代码比可执行文件小得多:

    $ ghc -O2 -ddump-simpl -fforce-recomp test.hs | wc -c
    49190 (bytes)
    

    因此,在程序中实际上并没有发现大量 Parsec,这是我最初的假设。

为什么可执行文件如此庞大?我能做些什么(动态链接除外)?

4

2 回答 2

14

要有效减小 Glasgow Haskell 编译器生成的可执行文件的大小,您必须关注

  • 使用-dynamic传递给 ghc 的选项的动态链接,因此模块代码不会通过利用共享(动态)库被捆绑到最终的可执行文件中。系统中这些 GHC 库的共享版本是必需的!
  • 删除最终可执行文件的调试信息(通过 GNU 的 binutils 的 strip 工具进行 fE)
  • 删除未使用模块的导入(不要期望在动态链接中获得收益)

简单的 hello world 示例的最终大小为 9 KiB,Parsec 测试大约为 28 KiB(均为 64 位 Linux 可执行文件),我觉得对于这样的高级语言实现来说非常小并且可以接受。

于 2012-10-04T15:08:41.293 回答
5

我的理解是,如果您使用包 X 中的单个函数,则整个包都会静态链接。我不认为 GHC 实际上是逐个函数链接的。(除非您使用“拆分对象”hack,它“往往会吓坏链接器”。)

但是,如果您要动态链接,那应该可以解决此问题。所以我不确定在这里建议什么......

(我很确定我在动态链接第一次出现时看到了一篇博文,展示了将 Hello World 编译为 2KB 二进制文件。显然我现在找不到这篇博文...... grr。)

还要考虑跨模块优化。如果您正在编写 Parsec 解析器,GHC 很可能会内联所有解析器定义并将它们简化为最有效的代码。而且,果然,你的几行 Haskell 已经产生了 50KB 的 Core。编译成机器代码时,它应该增大 37 倍吗?我不知道。您或许可以尝试查看后续步骤中生成的 STG 和 Cmm 代码。(对不起,我不记得我头顶上的编译器标志......)

于 2012-10-04T08:23:11.450 回答