使用GHC编译的小型Haskell程序生成了巨大的二进制文件。

138
即使是微不足道的Haskell程序也会变成巨大的可执行文件。
我编写了一个小程序,使用GHC编译后,大小扩展到7 MB!是什么原因导致即使是小的Haskell程序也要编译成庞大的二进制文件?
有没有什么方法可以减少它?

3
你试过只是剥离它吗? - Fred Foo
24
ињРи°Мstripз®ЛеЇПдї•еИ†йЩ§дЇМињЫеИґжЦЗдїґдЄ≠зЪДзђ¶еПЈи°®гАВ - Fred Foo
1
@tm1rbt: 运行 strip test。该命令会从程序中移除一些调试信息,使其变得更小。 - fuz
8
值得一提的是,出于性能原因,你应该使3D数学库中的数据类型更加严格:data M3 = M3 !V3 !V3 !V3data V3 = V3 !Float !Float !Float。编译时请使用ghc -O2 -funbox-strict-fields - Don Stewart
9
这篇帖子在meta上讨论。 - Patrick Hofman
2个回答

226

让我们看看发生了什么,试试

  $ du -hs A
  13M   A

  $ file A
  A: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), 
     dynamically linked (uses shared libs), for GNU/Linux 2.6.27, not stripped

  $ ldd A
    linux-vdso.so.1 =>  (0x00007fff1b9ff000)
    libXrandr.so.2 => /usr/lib/libXrandr.so.2 (0x00007fb21f418000)
    libX11.so.6 => /usr/lib/libX11.so.6 (0x00007fb21f0d9000)
    libGLU.so.1 => /usr/lib/libGLU.so.1 (0x00007fb21ee6d000)
    libGL.so.1 => /usr/lib/libGL.so.1 (0x00007fb21ebf4000)
    libgmp.so.10 => /usr/lib/libgmp.so.10 (0x00007fb21e988000)
    libm.so.6 => /lib/libm.so.6 (0x00007fb21e706000)
    ...      

你可以从ldd输出中看到,GHC生成了一个动态链接可执行文件,但是只有C库是动态链接的!所有的Haskell库都是逐字复制的。
另外:由于这是一个图形密集型应用程序,我肯定会使用ghc -O2进行编译。
你可以做两件事。
剥离符号
一种简单的解决方案:剥离二进制文件:
$ strip A
$ du -hs A
5.8M    A

Strip从目标文件中丢弃符号。它们通常仅用于调试。

动态链接的Haskell库

最近,GHC已经支持C和Haskell库的动态链接。现在大多数发行版都分发了一个构建以支持Haskell库动态链接的GHC版本。共享的Haskell库可以在许多Haskell程序之间共享,而不需要每次将它们复制到可执行文件中。

撰写本文时,Linux和Windows得到支持。

为了允许Haskell库进行动态链接,您需要使用-dynamic编译它们,如下所示:

 $ ghc -O2 --make -dynamic A.hs

另外,任何你想要共享的库都应该使用--enabled-shared参数进行构建:

 $ cabal install opengl --enable-shared --reinstall     
 $ cabal install glfw   --enable-shared --reinstall

你最终将得到一个更小的可执行文件,其中包含C和Haskell依赖项的动态解析。

$ ghc -O2 -dynamic A.hs                         
[1 of 4] Compiling S3DM.V3          ( S3DM/V3.hs, S3DM/V3.o )
[2 of 4] Compiling S3DM.M3          ( S3DM/M3.hs, S3DM/M3.o )
[3 of 4] Compiling S3DM.X4          ( S3DM/X4.hs, S3DM/X4.o )
[4 of 4] Compiling Main             ( A.hs, A.o )
Linking A...

而且,看这里!

$ du -hs A
124K    A

您可以剥离它以使其更小:

$ strip A
$ du -hs A
84K A

一个非常小的可执行文件,由许多动态链接的C和Haskell模块组成:

$ ldd A
    libHSOpenGL-2.4.0.1-ghc7.0.3.so => ...
    libHSTensor-1.0.0.1-ghc7.0.3.so => ...
    libHSStateVar-1.0.0.0-ghc7.0.3.so =>...
    libHSObjectName-1.0.0.0-ghc7.0.3.so => ...
    libHSGLURaw-1.1.0.0-ghc7.0.3.so => ...
    libHSOpenGLRaw-1.1.0.1-ghc7.0.3.so => ...
    libHSbase-4.3.1.0-ghc7.0.3.so => ...
    libHSinteger-gmp-0.2.0.3-ghc7.0.3.so => ...
    libHSghc-prim-0.2.0.0-ghc7.0.3.so => ...
    libHSrts-ghc7.0.3.so => ...
    libm.so.6 => /lib/libm.so.6 (0x00007ffa4ffd6000)
    librt.so.1 => /lib/librt.so.1 (0x00007ffa4fdce000)
    libdl.so.2 => /lib/libdl.so.2 (0x00007ffa4fbca000)
    libHSffi-ghc7.0.3.so => ...

最后一个要点:即使在仅支持静态链接的系统上,你也可以使用 -split-objs,以获取每个顶层函数一个.o文件,从而进一步减小静态链接库的大小。它需要使用带有-split-objs选项构建的GHC,但某些系统会忘记这样做。


7
GHC在Mac上的动态链接什么时候到来? - Carter Tazio Schonwald
1
默认情况下,cabal install 不会剥离已安装的二进制文件吗? - hvr
1
在Windows上这样做似乎会使生成的文件无法运行,它会抱怨缺少libHSrts-ghc7.0.3.dll。 - is7s
3
在执行这些操作后,这个二进制文件能在其他 Linux 机器上正常运行吗? - Incerteza
1
对于使用 Mac 的用户,相当于 ldd 命令的是 otool -L - James McMahon
显示剩余5条评论

13

Haskell默认使用静态链接。这意味着OpenGL的所有绑定都会被复制到您的程序中。由于它们非常庞大,您的程序会不必要地膨胀。您可以通过使用动态链接来解决此问题,尽管它并不是默认启用的。


5
您可以通过动态链接库来解决这个问题。不确定为什么默认值很重要,标志很简单。 - Thomas M. DuBuisson
5
问题在于,“你想要共享的任何库都应该使用 '--enabled-shared' 进行构建”,因此,如果你的 Haskell 平台附带的库是没有使用 '--enabled-shared' 构建的,则必须重新编译基础库,这可能会相当麻烦。 - nponeccop

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接