为什么Haskell/GHC可执行文件在文件大小中如此之大?

Dav*_*vid 16 haskell compilation ghc

可能重复:
用GHC编译成小二进制的小Haskell程序

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

  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')
    
    Run Code Online (Sandbox Code Playgroud)

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

    $ ghc -O2 -ddump-simpl -fforce-recomp test.hs | wc -c
    49190 (bytes)
    
    Run Code Online (Sandbox Code Playgroud)

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

为什么这么大的可执行文件?有什么我可以做的事情(动态链接除外)?

Dav*_*ric 12

要有效地减少格拉斯哥Haskell编译器生成的可执行文件的大小,您必须关注

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

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


Mat*_*hid 5

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

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

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

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