Universal Binary

最近 Ryan Gordon 终止了在 Linux 上实现 Fat Binary 技术的努力。这让我和甫鸼展开了一场对 Universal Binary 技术的讨论。

Universal Binary 的鼓吹者一般认为在连接器和操作系统底层直接加入对多 CPU 架构的支持( multi-arch support )可以大大简化开发者的负担和提高最终用户感受到的易用性。反对者则可以反驳说把能放在 build 脚本和 installer 中的 policy 不必要地塞到底层违反了 Unix 的原则。

抵触最大的恐怕是 Open Source 社区。毕竟这次 Ryan Gordon 放弃的直接原因就是 Linux 社区对其努力的冷落。Open Source 的发布方式可以戏称为 Universal Source —— 程序的大部分由 CPU 架构无关的源代码构成,少部分 CPU 构架相关的代码由条件编译构成或者由 build 脚本根据执行 build 的平台条件来引用不同的源文件。经由 Open Source 的安装三部曲(configure-make-make install),就能安装好一个适合目标硬件的精炼的无冗余的程序。这样的发布是完美的 —— 除了不能兼容非 Open Source 的商业模式。

要应对多 CPU 架构,就必需在程序的发布或者运行中实现应对这种架构的智能。Open Source 模式的优势在于它的发布形式能够完整的体现开发者在编写代码时就考虑到的一切应对 multi-arch 的智能,然后在安装过程中(也就是 build 过程中)由编译器、连接器和 build 脚本的强大智能来完成部署。非 Open Source 的发布模式则必须另辟蹊径。在所有面对 multi-arch 这个难题的企业中,Apple 当年的问题最为紧迫。由于要完成从 Power PC 到 Intel x86 的迁移,Apple 必须在一个时期之内能给开发者和用户一个足够简单(甚至傻瓜化)的解决方案。尽管如 Linux 社区大力主张把 multi-arch 的智能放到 build 脚本和 installer(Linux 的 package manager)为正途,而 Mac OS X 在很多方面也确实遵循 Unix 的文化,最终 Apple 还是选择了 Universal Binary 。因为我每日都在 OS X 上开发,已经习惯 Universal Binary,到了有所偏爱的程度。

为了比较各种方案的差异,设定一个例子。有一个工程,在单一 CPU 架构的时候生成四个可执行文件,六个数据文件,一共十个文件,为其增加 multi-arch 支持。假设我们拥有一个不支持 Universal Binary 的操作系统。当然相应的编译器和连接器也就不会支持 Universal Binary。但是我们仍然假设编译器和连接器的交叉编译功能很方便,只要一个 Option 就能切换目标文件的 CPU 种类。也就是说支持不同 arch 的基础能力具备,只是没有对 Universal Binary 的支持。那么,为了服务使用不同 CPU 架构的用户,我们就必须面临几种选择。第一是我们使用一套标准的工程文件,针对需要支持的每种 CPU 架构,用相应的编译器 Option 编译一次。假设有三个 CPU 构架。因为是采用完全相同的工程文件,仅仅是一个编译目标的 Option 不同,所以我们会生成 30 个文件(三个版本,每个版本十个文件)。使用不同 CPU 的用户可以选择不同的版本安装。这样的方案,build 过程中对六个数据文件(假设是平台无关的格式)的生成就是冗余的。对于拥有多于一种 CPU 架构的机器的用户来说,需要下载相应的多个版本,所以重复下载了六个数据文件(重复的次数由用户拥有的 CPU 种类决定)。

另一种选择是我们写一套特殊的工程文件。一次生成支持三个 CPU 的 12 个可执行文件(每个架构四个),而对于平台无关的六个数据文件只生成一套。所有的结果都打入一个 package。相应的,installer(或者说 unpackager)必须针对用户安装时的平台提取正确的可执行文件版本和数据文件。这种方案的缺点是必须自己在 build 脚本和 installer 里编写处理 multi-arch 的智能。Universal Binary 的反对者会说这些智能并不复杂,Build 脚本和 installer 正是实现它们的好地方。但是,实际上每个工程实现这些智能的方式都会有些微的区别。这就意味着,用户从一个产品得到的知识并不能用到另一个产品里 —— 比如检查该产品支持的所有架构,如果是 Universal Binary 的话可以用统一的命令查询。开发者从一个工程转向另一个工程也必须通过文档或者别人的描述才能熟悉这个工程特有的 multi-arch 处理。

对于上述的工程例子,支持 Universal Binary 的系统则是一次生成十个文件,只在四个可执行文件中同时包含三种 CPU 架构的代码(用 ELF 的术语来说是三个 section )。这种方式为反对者诟病之处在于,安装好的系统存在冗余 —— 磁盘上会有用不到的机器码,提高了连接器和操作系统的复杂度,违反了 Unix 的 Mechanism, not policy 的原则。不过,任何稍稍违背这一原则的做法都会带来一个好处 —— 标准化带来的统一性。在《C++与垃圾回收》一文中我曾经说过,正是因为 C++ 把各种高级内存管理策略放到了库而不是语言级别,才让这些高级策略不能得到强制统一而形同虚设。所以,mechanism-not-policy 需要灵活的「道」来处理而不能一味的极端化。

Universal Binary 其实可以看作一种次优的方案。它的冗余度没有达到最优(与定制编写 build 脚本和 instaler 相比),但是大大低于第一种方案。即使存在冗余度,冗余的粒度也很小,属于次文件级别(ELF 格式为例,即 section 级别)。而且这个冗余度也带来了一些智能 installer 方案不具备的好处,就是安装之后的程序可以在不同架构的 CPU 的机器之间自由拷贝(前提是程序具备这种在任意位置运行的能力,而 Mac OS X 的 Single-File Application 恰恰是这种风格)。Universal Binary 还是一个非强制性使用的功能。连接器和操作系统只是提供支持 multi-arch 的能力,选择打入哪些 CPU 架构的执行码完全由开发者自己决定。从这个角度来说,Universal Binary 并不是真的违背 mechanism-not-policy 的原则。Universal Binary 产生于 Mac 的特殊历史,也与其文化相辅相成 —— Single-File Application,也就是 installer 完全零智能;无中央化的包管理(通过 Guide 来保证用户体验的一致);相当数量的用户(由于 transition 阶段的存在)拥有或者在一段时间拥有基于不同 CPU 架构的多台机器,需要程序能够在机器之间自由拷贝并保持工作(由于 Single-File Application 的文化)。

Universal Binary 是一个能提供有别其它方案的独特优势的方案,当然也引入了自身的代价。综合考虑,这些代价在 Mac 特有的文化中是完全可以接受的,也就是说 Universal Binary 在 Mac 的发展中是最好的选择。至于这种收益代价的比例在其它操作系统上如何还有待考验。不过我认为其它系统应该保持一种开放的态度。

2条回应 to “Universal Binary”

  1. fzhang Says:

    总之, Universal Binary针对Mac本身的历史与文化, 是一种现实/简单/有效的multi-arch解决方案. 那么, 在Linux上实现Universal Binary的意义何在? “Universal Binary 的鼓吹者” 又能鼓吹什么呢?

  2. fuzhou Says:

    我和东哥德争吵其实大抵上源于这个看法:

    a) Single-File Application,也就是 installer 完全零智能;
    – Linux下installer需要承担的任务非常多,dependency,versioning,etc
    b) 无中央化的包管理(通过 Guide 来保证用户体验的一致);
    – yum,apt,pacman,举凡能报得上来名字的近5年来的Linux包管理系统,包括BSD自己古老的Ports,几乎个个都倾向于中央化管理。

    c) 相当数量的用户(由于 transition 阶段的存在)拥有或者在一段时间拥有基于不同 CPU 架构的多台机器,
    – 这个得看情况,我不好说。

    d) 需要程序能够在机器之间自由拷贝并保持工作(由于 Single-File Application 的文化)。
    – 传统上UNIX出于安全问题一贯是反对自由拷贝可执行代码的,无论是脚本还是二进制文件。

    简单地说,上述四项中至少有三项与Linux下典型的用户行为完全相反,我想只要是认真考虑过的人,我估计很难有人相信在Mac下大放异彩的Fat Bin在Linux下仍然是必要的:用户模型可说是截然相反却用相同的解决方案?

    如果我的怀疑是有道理的,那么我对最后的“过我认为其它系统应该保持一种开放的态度”就必须表示反对了。原来文章里只是说当Ryan同学试图将Fat Bin提交给内核组的时候受到冷遇而已,没人说Ryan不能自行维护一个分支来提供这个功能,谈不上什么开放和封闭。

    另外得说的是,像fat bin这种提案不单牵涉到内核,我相信所有的二进制库和编译工具链都得跟着做出改变,这种修改和协调能力显然远远超出了Linux内核组能控制的范围。在“集市”开发模式下,指望多个开发组(至少包括GCC,Linux,GLibc,还有几乎所有包管理系统)为了一个效果好坏还尚未可知的新功能大幅度修改自己的代码,实在不现实——相反地,这倒是Microsoft这种教堂风格的公司可能会去干的事情,比如Isolated Credential。

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

您正在使用您的 WordPress.com 账号评论。 注销 /  更改 )

Facebook photo

您正在使用您的 Facebook 账号评论。 注销 /  更改 )

Connecting to %s


%d 博主赞过: