5
11
2015
28

为什么我反对普遍地静态链接?

本文来自依云's Blog,转载请注明。

这是我查看知乎私信时不小心瞅到的问题所触发的。由于 Go 在国内的兴起,我对这个问题也多有思考,就放在这里记录一下好了。知乎的链接我就不贴了,带 nofollow 的都懒得贴了。

首先,我们搞清楚问题是什么。或者说,我反对的究竟是什么?

静态链接,即早期唯一的一种链接出二进制可执行文件的方式,把所有程序需要用到的库全部打包到一个文件里边。后来,由于存储空间越来越不够用,所以发展出了动态共享库,也就是把库编译成 so、dll 或者 dylib 这种由动态库装载器在程序运行前或者运行时进行链接的方案。

静态链接的优点

  1. 方便分发,不会因为库的升级而导致程序无法运行。这一点没有严格指定依赖版本的 Arch Linux 用户应该都有所体会。当你更新某个库(比如 boost 或者 icu 什么的)之后,动态链接到旧库的程序会出错。

  2. 效率稍高。这个反正人类是体会不到的。

基于第一点,用于急救的重要程序最好使用静态链接,特别是 busybox。以前我会安装 busybox 的,后来因为 Arch 改用动态链接 C 库了,对于我不再有意义,所以卸载了。以后 C 库如果出问题就直接重启进救援系统了。

另外我还有静态链接的 32 位 Vim,为的是在 Vim 依赖库更新而 Vim 没更新时依旧有个顺手的编辑器可用。我一直自行编译 Vim 因此这个曾经十分有用。不过由于现在对经常变动导致问题的解释器支持采用运行时动态链接,所以基本不受影响了。

静态链接的缺点

这个可以列出长长的一串了。

  1. 占用磁盘空间。我就不怎么喜欢 Haskell 写的程序,太占硬盘了,一个程序就几十M。当然换新电脑之后目前硬盘空间有富余。但是它们还是会渐渐被我的各种源码和虚拟机什么的填满的。

  2. 占用内存空间。可执行文件在执行时是需要映射到内存中的。如果使用动态链接,那么因为是同一文件,所以在内存时只需要映射一份就可以了。而静态链接,不仅因为来源于不同的文件而需要加载、映射多次,而且因为来自于不同的构建等原因,逻辑上相同的代码往往并不会造成映射之后的内存页相同,使得内存去重机制(如 UKSM)失效。

    别说内存是白菜价,除非你来给我手上的笔记本、VPS、服务器、路由器、单板机等都配置个几十G的内存,我付给你等质量的白菜。还记得比尔·盖茨说过的话吗——「640K足够了」。够了吗?

  3. 占用 I/O 带宽。可执行文件越大,在内存里没有缓存时需要从外存读取的数据也就越多,耗时也就越长。而因为文件体积增大,内存资源越发不够用,I/O 缓存越少,导致缓存命中更低。

  4. 占用网络带宽。你可执行文件是从网上下的吧?你在国内看个视频还挺流畅,但是到世界各地去下软件你试试看?

  5. 运行时链接。我写了一个程序,支持 MySQL、SQLite3、PostgreSQL、MongoDB、Oracle 等等等等数据库。但是你显然不会用到所有的数据库支持吧?那你为什么要所有这些数据库的连接库来用一个 SQLite3 数据库的功能呢?使用运行时链接(dlopen 那些函数),程序可以在运行的时候动态判断并加载它此次运行所需要的动态库。

  6. 升级。openssl 爆出了一个很严重的安全漏洞,已经被修复了,你怎么办?当然是升级呗。那你希望是更新一个几M的包然后重启服务器解决问题,还是下载好几百M的程序、更新每一个你所用到的使用了 openssl 的程序?更何况那些程序本身不一定都更新了,也许为了安全你得自行编译其中的很大一部分(你可以期望有一个安全团队在半夜爬起床去更新一个软件,但是你觉得上千项目的开发者都会这么做吗)。你也不一定能够找到所有静态链接了有漏洞的 openssl 版本的程序,万一漏掉一个,你整个服务器的安全性就没了(所以 openssl heartbleed 漏洞更新之后建议是重启系统而不是重启各服务)。

更别说更底层的库了,比如 C 库或者 C++ 库。不至于人家更新了一行代码,你就要重装整个系统吧?

静态链接有它自身的用处,但是它并不适合所有情况,甚至并不适合大多数情况。动态链接以其微小的运行效率损失为代价,为不论是最终用户还是开发者、打包者提供了更为优秀的库管理方案。之所以很多人看到静态链接相对于动态链接的优势,我认为还是因为他们没什么机会看到静态链接、尤其是大量静态链接会带来的问题。

你不需要把程序都静态链接。你需要的只不过是一个优秀的包管理器和维护团队而已

Category: Linux | Tags: linux 编译 go 编程语言 | Read Count: 17876
StarBrilliant 说:
May 11, 2015 11:31:44 PM

我对于 Windows 一般是静态链接的,除非作为 plugin(你的第五条)。
因为 Windows 用 DLL 并不能省内存(使用了 rebase 机制而不是 PIC)。
而且 Windows 自身的 DLL 管理烂得跟翔一样,没有 DLL 版本签名机制,自带的 WinSxS 也只能管理微软自家的 DLL 版本。

Avatar_small
依云 说:
May 11, 2015 11:49:11 PM

哦,不了解 Windows 那边的情况。怪不得 Windows 那么占内存呢。

御宅暴君 说:
May 12, 2015 01:25:22 PM

我用 Arch Linux 很久了,其包管理和维护确实优秀到不需要为其打包成静态链接程序。

自由建客 说:
May 21, 2015 09:02:37 PM

FreeBSD 的 /rescue 目录牛逼,里面一堆命令,其实就一个文件,也就是那些命令的功能都写在了一个文件里。从而避免了每个命令单独静态连接导致的体积膨胀。

Avatar_small
依云 说:
May 21, 2015 09:31:27 PM

和 Linux 的 busybox 类似嘛。

ThomasLau 说:
May 24, 2015 06:22:35 PM

我觉得这更像是历史遗留问题...还有编译脚本:D,

intijk 说:
Jun 05, 2015 02:12:48 AM

有很多时候都在想要是linux的软件包像一些绿色windows小软一样就好了,就一个文件夹,打开,双击,运行,虽然更新什么的问题都是存在的,但是至少能无障碍的跑起来。

比如很多老游戏,在steam上分发的时候,可能大家都不是很关心bug了,能不缺包的跑起来就是造福了,单机的,崩了重启呗,最怕的就是缺这缺那, 放依赖让系统解决,linux千变万化,解决不了这个程序就是个死程序了,这种情况真的希望所有东西都是静态无依赖的。

Avatar_small
依云 说:
Jun 05, 2015 08:04:20 AM

但那种双击就可以直接跑的是少数软件。Linux 也有不少带一堆依赖的商业软件。

首先呢,「静态链接」≠「无依赖」。除了依赖内核之外,有些程序还会在依赖关系不对的时候莫名其妙地崩溃。因为是静态链接的发布版,所以 gdb bt 出来的全是问号,也搞不清楚到底是哪个库太新了。

其次呢,你真觉得在互联网如此发达的情况下,带着个几十G的U盘到处跑很有趣?

在 steam 上分发怕出依赖问题——谁让你用 steam 的嘛。你去弄些个 deb、rpm 包,在 AUR 上上传个 PKGBUILD 什么的就好。如果你的程序比较受欢迎而又是自由的话,会有人帮你做这些的。

Avatar_small
依云 说:
Jun 05, 2015 08:07:07 AM

Linux 下,至少是自由软件,在有网络的情况下要运行起来很简单:一句命令把它装好,另一句命令把它运行起来。

那些不需要安装的所谓「绿色」软件,和系统的集成度太低,右键菜单什么的不会有,默认文件关联也不会有,不知道为什么你们都还那么喜欢。

Avatar_small
依云 说:
Jun 05, 2015 08:07:48 AM

不会你们都跟我当年一样没有属于自己的电脑只能到处蹭吧?

intijk 说:
Jun 06, 2015 12:39:02 AM

我觉得小模块无依赖,咱就说绿色吧,确实是一种优势。

我觉得和一个能正确运行的程序本身的功能比起来,系统集成度,右键什么的并不重要。

它能运行起来是最重要的。

Avatar_small
依云 说:
Jun 06, 2015 10:16:59 AM

的确,它能正确地运行起来最重要。而很多软件按流程安装比所谓的「绿色版」能够正确地运行的概率更高,能够正确工作的功能也更多。

小白 说:
Jun 06, 2015 09:16:16 PM

额 很明显静态编译是和linux操作系统的思维是违背的。
golang为了效率做了太多类似的事情,具体有没有效果就不知道了?

它的解释: 1.程序自己管理内存,免除操作系统对它的影响 2.更加适应lxc的环境

反正总之golang就是一个为了解决问题的极端语言,绿色版本之类的其实也不是它的目的。不过如果是其他人都跟着做,那就看他们是不是也是有类似的需求了

intijk 说:
Jun 07, 2015 03:00:28 AM

不要误会,我不是来引起争执的。
我也在obs上给软件打包,算是入门packager吧,我确实希望软件能够符合发行版规范来提供tarball,并且提供维护和更新。

不过不得不说,这样的成本确实是比静态高的,静态的缺点非常明显,内存占用+不能更新依赖。 但怎么说呢,memory is cheap today.

但是在很多软件,对的,就是你说的商业软件,还有游戏啊什么的,它们倾向于发布静态链接也是有苦衷的,付费的用户肯定希望下载之后直接就能运行的软件。按照我们的思路,完美的方式是提供一个repo,并给用户一个key,然后用户使用自家的包管理工具直接apt-get install或者yum install这样的。

1. 不是所有用户都会这些啊,就算有yast一键之类的,依然过程很繁琐的。
2. 为各个发行版都这么做一遍,这成本就上来了啊。更何况还要考虑统一发行版不同版本,而且用户万一自己的默认官方源弄坏了,或者就说,用户的发行版的源的mirror server down了,由于依赖的问题,你刚买的photoshop不能跑,这多揪心,他要退钱怎么办?
3. 软件是人写的,总有一天会缺少维护,它依赖的一个 libexample升级之后deprecate了一个函数,这软件调用了这么一个函数,完了,能满足依赖,但是满足不了版本了,你的包管理器的多版本管理好用吗?恕我愚笨,我反正是不会用。新旧版本之间有冲突的话包管理器也无能为力。这个独立小游戏的作者不写了,或者.....死了。我以后就得用虚拟机去玩这个游戏了.....

以上这些原因确实客观存在,所以我觉得各个发行版的包管理并不是特别优秀,站在developer的角度讲,他用他的智慧贡献了最宝贵程序的灵魂,但他可能确实不擅长打包,他如果用了静态链接来发布他的程序,请原谅他的缺陷,但我不觉得他是有原罪的。因为怎么说呢... Linux各个发行版的设计其实并不是很developer friendly的,尤其是有这么多发行版....

扪心自问我们这些发行版的设计,是的,包依赖的解决像数学公式一样解决的很好,如果你是一个活跃的lib开发者,是这颗树上的一个节点,你将得到很好的照顾。但是如果你是一个app开发者,是最顶层的,你依赖别人,别人不依赖你,你的使命是给你的用户提供软件,你是否愿意信赖底下这棵庞大的依赖树,把自己的发布/市场/盈利绑定在上面?就我看来,不太乐观啊,从风控的角度来讲,要是我,我就可耻的用静态链了啊。

所以我觉得发行版的packager都很了不起,他们用自己的力量在做一些... 额,脏活累活,为能把软件在不同的发行版跑起来贡献自己的能量。向Felix, hillwood等同学致敬。

docker这样的设计我觉得是以后的一个方向, 商业软件可以选定docker(也就是environment)来开发自己的程序,而即使以后缺乏维护等停止开发,也有image可用。(这其实跟虚拟机没区别了,但是docker比虚拟机的时空开销小太多了 :)

就说这些。

intijk 说:
Jun 07, 2015 03:05:34 AM

互联网如此发达的今天,我希望所有的软件都消失在local, 存储在云端,我打开浏览器,登录我的帐号,就什么都能用。

Avatar_small
依云 说:
Jun 07, 2015 08:20:11 AM

对,商业软件经常会因为发行版太多还经常更新的原因选择使用静态链接或者把依赖打包在软件里。但是开源软件就不必须这么做了——足够好的开源软件会有打包者的。

Avatar_small
依云 说:
Jun 07, 2015 08:26:17 AM

Docker 也有自己的问题: http://www.banyanops.com/blog/analyzing-docker-hub/

而且空间占用和静态链接差不多的。

我很讨厌别人说内存便宜、磁盘便宜。刚查了一下,一条 8G 的得好几百呢,而且你还不能无限地加。另外永远不要忘记了,当你开机使用久一点之后,你的内存使用率会接近 100% 并且大部分情况会保持在那个水平。

Avatar_small
依云 说:
Jun 07, 2015 08:30:40 AM

这也是我所讨厌的,特别是在中国,这个用户数据安全根本没什么人关心的神奇国度。

另一点是,也许美国和日本的互联网是很发达,但绝对不是中国大陆。有 Wi-Fi 的地方你还得忍受一些运营商的各种劫持和限制,访问国外网站就算不撞墙也经常卡成翔。没 Wi-Fi 的地方更悲剧了,各种没 3G 信号。有也不敢大用,流量费用还贵着呢。

小白 说:
Jun 07, 2015 08:45:08 AM

所以不能所有软件都静态编译,docker之类的软件一台服务器只有少数就好,你的论据是成立的。反对普遍

f 说:
Sep 06, 2015 08:41:54 AM

其实某种程度上这可以理解为一个松耦合和紧耦合的问题。
几乎又是一个永远扯不清的问题,各有利弊。
长期上我倾向于尽量多用动态链接,各种复用带来的好处是多多的。
然而只要是人,人做的事就不可能完美。优秀的团队也会有坑。而这些坑要么就别踩到,一切轻松愉快,一旦踩到的时候,就只会直翻白眼想为什么不直接打包静态链接库不就没这些破事了。

kitt 说:
Nov 05, 2015 05:20:00 PM

有感触啊,Docker单独的一个base image确实不大,不到200M,但是啥都没有,没有vim,which命令、file命令都没有,这库那库的装完image大小就上去了,而且只支持Linux系,想跑个.exe也无法...

Avatar_small
依云 说:
Nov 05, 2015 07:24:23 PM

这么简陋的系统还有 200M 啊,Tumbleweed 的网络安装程序不到100M呢,它还能连 Wi-Fi。

hexchain 说:
Apr 15, 2016 09:19:44 PM

比尔盖茨没说过那句话… https://en.wikiquote.org/wiki/Bill_Gates#Misattributed

KireinaHoro 说:
Nov 17, 2017 06:02:29 PM

其實應該是因爲 livecd 裏面有用到 squashfs 的壓縮(

Avatar_small
依云 说:
Nov 17, 2017 09:43:24 PM

docker 的镜像也是有压缩的呀。

woobamboo 说:
Aug 03, 2018 05:00:23 PM

学习了,感谢!用了Linux作服务器两年,机器太老,各种依赖真的让人难受。看到了这么多优点,现在很理解其存在的意义了。


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

| Theme: Aeros 2.0 by TheBuckmaker.com