即使是很小的 Haskell 程序也会变成巨大的可执行文件。
我已经编写了一个小程序,它被编译(使用 GHC)为大小扩展为7MB 的二进制文件!
什么可以导致即使是一个小 Haskell 程序被编译成巨大的二进制文件?
如果有的话,我能做些什么来减少这种情况呢?
Haskell 默认使用静态链接。也就是说,所有到 OpenGL 的绑定都被复制到您的程序中。由于它们非常大,您的程序会不必要地膨胀。您可以通过使用动态链接来解决这个问题,尽管默认情况下不启用动态链接。
看看怎么回事,试试
$ 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 库都是逐字复制的。
ldd
旁白: 由于这是一个图形密集型应用程序,我肯定会用 ghc -O2编译
ghc -O2
你可以做两件事。
剥离符号
一个简单的解决方案: 去掉二进制:
$ strip A $ du -hs A 5.8M A
从目标文件中删除符号。它们通常只用于调试。
动态链接的 Haskell 库
最近,GHC 获得了对 C 和 Haskell 库的动态链接的支持。现在,大多数发行版都发布了为支持 Haskell 库的动态链接而构建的 GHC 版本。共享的 Haskell 库可以在许多 Haskell 程序之间共享,而不必每次都将它们复制到可执行文件中。
在编写本文时,支持 Linux 和 Windows。
为了允许 Haskell 库进行动态链接,您需要使用 -dynamic编译它们,如下所示:
-dynamic
$ ghc -O2 --make -dynamic A.hs
另外,任何你想要共享的库都应该使用 --enabled-shared来构建:
--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 => ...
最后一点: 即使在只有静态链接的系统上,也可以通过 使用-拆分-对象得到一个。O 文件每个顶级函数,这可以进一步减少静态链接库的大小。它需要在 GHC 上构建-split-object,而有些系统忘记了这一点。