【问题标题】:Why are Haskell/GHC executables so large in filesize? [duplicate]为什么 Haskell/GHC 可执行文件的文件大小如此之大? [复制]
【发布时间】:2012-09-25 00:19:24
【问题描述】:

可能重复:
Small Haskell program compiled with GHC into huge binary

最近我注意到 Haskell 可执行文件有多大。以下所有内容均在 GHC 7.4.1 上编译,在 Linux 上使用 -O2

  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,这是我最初的假设。

为什么可执行文件如此庞大?有什么我可以做的吗(动态链接除外)?

【问题讨论】:

  • @DanielWagner 另一个问题当然是相关的,但即使使用那里描述的技术 Hello World 仍然是巨大的。另外:为什么应该包含整个程序的小核心代码在编译时会变得如此之大?
  • 有一个相当大的运行时系统。
  • @David:核心不包含整个程序,除非所有内容都被内联,这是不太可能的。所以它将在 Parsec 中链接,除非你使用 -split-objs 构建它(请参阅 related answer),否则它必须链接所有这些。
  • 作为参考,您的原始示例在我的系统上生成了 29 KiB "big" 可执行文件。 ghc -O2 -dynamic test.hs &amp;&amp; strip test &amp;&amp; du -b test => 28712 字节。 GHC 7.4.2 版本,x86_64 Linux 系统。

标签: haskell compilation ghc


【解决方案1】:

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

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

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

【讨论】:

  • 如果我与-dynamic 链接,Hello World 只有 9 KiB。在 Parsec 案例中,我在安装动态版本时遇到问题(cabal install parsec --enable-shared --reinstall 导致 cabal 抱怨我没有“用于包 `mtl-2.1.1' 的 dyn 库”,但这会产生另一个问题。在任何案例,谢谢。
【解决方案2】:

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

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

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

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

【讨论】:

  • 实际情况并非如此。这取决于系统。在大多数具有静态链接的系统上,GHC 使用“拆分对象”,以便每个函数获得一个对象。
  • @DonStewart 但是您需要在 cabal 配置中启用 split-objs 才能使用拆分对象构建您安装的 cabal 库,不是吗?
猜你喜欢
  • 2015-05-14
  • 2012-09-02
  • 2012-06-14
  • 1970-01-01
  • 1970-01-01
  • 2012-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多