为什么GHC如此庞大?

153

有一个简单的答案吗:为什么GHC这么大?

  • OCaml: 2MB
  • Python: 15MB
  • SBCL: 9MB
  • OpenJRE - 26MB
  • GHC: 113MB

不关心"如果Haskell是正确的工具,我为什么不应该在意大小"的鼓吹,这是一个技术问题。


1
你从哪里得到这500MB的信息?我的GHC远没有那么大。 - Jacob
除非你把所有的库都算上,不然我猜就不算了... - Jacob
抱歉,我是根据包管理器下载的一些依赖项进行的。我已经更新了它以反映从网站下载的大小。我添加了一个编辑摘要,但它还没有出现在这里(还没出现吗?)。我认为问题仍然存在。它很大。 - Christopher Done
20
也许我们应该将苹果和苹果比较,将橙子和橙子比较。JRE是一个运行时环境,而不是开发工具包。OpenJDK 7源代码包大小为82MB(http://download.java.net/openjdk/jdk7/),而GHC 7源代码包大小为23MB(http://www.haskell.org/ghc/download_ghc_7_0_1)。现在来看运行时环境:在Ubuntu上,openjdk-6-jre-headless解压后大小为77MB,而Haskell的helloworld与其运行时静态链接,小于1MB。 - sastanin
今天我对2014年的大小感到好奇。看起来这个论点仍然成立。我找到了以下链接:1.GHC http://www.haskell.org/ghc/download_ghc_7_8_3;2.OpenJCK http://packages.ubuntu.com/precise/openjdk-7-jdk - AnneTheAgile
6个回答

194

这有点傻。 GHC提供的每个库都至少有四种版本

  • 静态版
  • 动态版
  • 已进行性能分析的版本
  • GHCi版

GHCi版本只是将静态版本链接在一起形成单个.o文件。其他三个版本都有自己的接口文件(.hi文件)。进行性能分析的版本似乎比未经过性能分析的版本大约两倍(这有点可疑,我应该研究一下为什么)。

请记住,GHC本身是一个库,因此您会得到4份GHC副本。不仅如此,GHC二进制文件本身也是静态链接的,这就是5份GHC副本。

我们最近使得GHCi可以使用静态的.a文件。这将允许我们摆脱其中的一个版本。从长远来看,我们应该动态地链接GHC,但这需要更大的改变,因为这将意味着动态链接将成为默认选项——与C语言不同,对于GHC,你必须预先决定是否要进行动态链接。我们还需要进行更多的改变(例如在Cabal和包系统等方面),才能让这变得真正实用。


17
我原以为这全是 Haskell 提供的逻辑:惰性求值、类型推断等。 - mcandre
4
因此,113MB / 4 ~= 28MB,仍然比OpenJRE大...但考虑到GHC与OpenJDK相当,而不仅仅是JRE,这让我感觉更好。 - Earth Engine
1
现在我想 GHC 使用动态链接,也许 Simon Marlow 博士关于四种风味压缩的想法更实际?引用:1.#3658 (Dynamically link GHCi (and use system linker) on platforms that support it) – GHC https://ghc.haskell.org/trac/ghc/ticket/3658; 2.#8266 (Dynamic linking on Mac) – GHC https://ghc.haskell.org/trac/ghc/ticket/8266 ; 3.#8376 (Static Executable + GHC API (+ Dynamic Linking?) gives Segfault) – GHC - AnneTheAgile

58

我们应该将苹果与苹果,橙子与橙子进行比较。JRE是一个运行时环境,而不是开发工具包。我们可以比较:开发工具包的源代码大小,编译后的开发工具包大小以及最小运行时的编译后大小。

OpenJDK 7源代码包为82 MB(download.java.net/openjdk/jdk7),而GHC 7源代码包为23 MB(haskell.org/ghc/download_ghc_7_0_1)。这里GHC不算大。运行时大小:Ubuntu上的openjdk-6-jre-headless未压缩大小为77 MB,而Haskell helloworld与其运行时静态链接,大小小于1 MB。在这里,GHC也不算大。

GHC体积庞大的地方在于编译后的开发工具包大小:

GHC disk usage

GHC本身占用270 MB,加上所有一起提供的库和实用程序,则超过500 MB。是的,它很大,即使带有基础库和构建工具/依赖管理器。Java开发平台则更小。

GHC:

$ aptitude show ghc6 | grep Size
Uncompressed Size: 388M

使用withdependencies选项来针对OpenJDK进行编译:

$ aptitude show openjdk-6-jdk openjdk-6-jre openjdk-6-jre-headless ant maven2 ivy | grep Size
Uncompressed Size: 34.9M
Uncompressed Size: 905k
Uncompressed Size: 77.3M
Uncompressed Size: 1,585k
Uncompressed Size: 3,736k
Uncompressed Size: 991k

但它仍然超过100 MB,而不是你所写的26 MB。

ghc6和ghc6-prof中的重量级内容有:

$ dpkg -L ghc6 | grep '\.a$' | xargs ls -1ks | sort -k 1 -n -r | head -3
57048 /usr/lib/ghc-6.12.1/ghc-6.12.1/libHSghc-6.12.1.a
22668 /usr/lib/ghc-6.12.1/Cabal-1.8.0.2/libHSCabal-1.8.0.2.a
21468 /usr/lib/ghc-6.12.1/base-4.2.0.0/libHSbase-4.2.0.0.a
$ dpkg -L ghc6-prof | grep '\.a$' | xargs ls -1ks | sort -k 1 -n -r | head -3
112596 /usr/lib/ghc-6.12.1/ghc-6.12.1/libHSghc-6.12.1_p.a
 33536 /usr/lib/ghc-6.12.1/Cabal-1.8.0.2/libHSCabal-1.8.0.2_p.a
 31724 /usr/lib/ghc-6.12.1/base-4.2.0.0/libHSbase-4.2.0.0_p.a

请注意 libHSghc-6.12.1_p.a 有多大。因此,似乎对于每个库都有静态链接和分析版本的答案。


9
我猜测——大量静态链接。每个库都需要静态链接其依赖项,这些依赖项又需要静态链接它们的依赖项,以此类推。而且这些代码通常会编译多次,包括带和不带剖析的版本,即使没有剖析,二进制文件也没有被剥离,因此包含了大量的调试信息。

2
如果 GHC 转向类似 jhc 的整个程序重新编译几乎所有内容的模型,我可能不会介意。如果能防止 'ld' 交换,它甚至可能编译得更快。 - John L

8

因为它捆绑了gcc和一堆库,全部都是静态链接的。

至少在Windows上是这样。


12
不,它只依赖于gcc,而不是Linux。因为Windows没有在其“分发”中包含gcc,所以它必须随ghc一起提供。 - comonad
更准确地说,这是因为Windows有rpm但没有yum,所以没有简单的方法来获取依赖项,因此每个应用程序都像Docker/Snap一样捆绑了所有依赖项。此外,虽然存在MSI依赖项,但由于MSI的过度设计,它们很少被使用。当新的打包技术最终被采用时(可能是AppX的继任者),这种情况可能会改变。 - nponeccop

5

简短回答是因为所有可执行文件都是静态链接的,可能包含调试信息,并且库被包含在多个副本中。其他评论者已经说过这一点。

动态链接是可能的,它将大大减小体积。以下是一个例子Hello.hs

main = putStrLn "Hello world"

我在Windows上使用GHC 7.4.2进行构建。

ghc --make -O2命令生成大小为1105K的Hello.exe文件。

对其运行strip命令后,大小缩小至630K。

ghc --make -O2 -dynamic命令生成大小为40K的文件。

对其运行strip命令后,大小仅剩下13K。

它依赖于5个dll文件,未压缩时总大小为9.2 MB,压缩后大小为5.7 MB。


ghc版本9.0.1,Hello.exe大小为11,880,549字节。8.x版本略小,大约在10MB左右。 - zumalifeguard
在Ubuntu上,它生成一个大小为896,168的静态图像。 使用-dynamic参数则为16,592。 但在Windows上无法使用-dynamic参数。 - zumalifeguard
1
7.6.3是最后一个配备动态“base”的GHC版本。由于各种原因,动态链接随后被破坏,然后他们部分修复了它,现在可以制作动态的hello world,但您必须构建支持的自己的GHC,因为自7.6.3以来多年来它还不够成熟。另外,您能找到剥离大小吗? - nponeccop

5

6
让我补充一点:如果您只使用基本编译器,并剥离不必要的任何内容(例如构建未进行配置文件分析、剥离等的编译器),则可以将其减小到约5 MB。但请尝试将编译器大小与GCC进行比较。(编辑了评论,所以不得不删除它...抱歉) - fuz

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