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

时间:2012-10-04 01:32:34

标签: haskell compilation ghc

  

可能重复:
  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,这是我最初的假设。

  3. 为什么这么大的可执行文件?我能做些什么(动态链接除外)?

2 个答案:

答案 0 :(得分:12)

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

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

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

答案 1 :(得分:3)

我的理解是,如果您使用包X中的单个函数,整个包将静态链接。我不认为GHC实际上是逐个函数链接。 (除非你使用“拆分对象”黑客攻击,否则“会使链接器出现问题”。)

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

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

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