写点什么

连 1.0 版本都没有,Uber 为什么会采用这样一项新技术?

  • 2022 年 6 月 06 日
  • 本文字数:4610 字

    阅读完需:约 15 分钟

连1.0版本都没有,Uber为什么会采用这样一项新技术?

本文最初发布于 Motiejus Jakštys 的个人博客。

免责声明:我在 Uber 工作,我的一部分职责是将zig cc引入公司。但这篇文章是我的观点,与 Uber 无关。

我日前在 Zig 的一场交流会上作了题为“Uber 引入 Zig”的演讲。本文从技术和社交两方面简单介绍了“Uber 是如何使用 Zig 的”,而主要的篇幅是介绍“我把 Zig 带到 Uber 的经验”。


本文要点:


  • Uber 使用 Zig 来编译其 C/C++代码。现在,Uber 只在Go Monorepo中使用bazel-zig-cc,但计划尽可能地将zig cc推广到其他需要 C/C++工具链的语言。

  • 与其他工具链相比,zig-cc 提供的 C/C++工具链的主要优势是:glibc 版本可配制与 macOS 交叉编译。

  • Uber 没有任何使用 zig-the-language 的计划。

  • Uber 与 Zig 软件基金会(ZSF)签署了一份支持协议,以优先修复我们提交的 Bug。ZSF 的财务报告有披露了合同额。

  • 感谢我的团队、Go Monorepo 团队、Go 平台团队、我的主管、财务、法律,当然还有 Zig 软件基金会,是他们让这种关系成为现实。到目前为止,这种关系已经带来了丰硕的成果。


Uber 技术栈简介

Uber 于 2010 年创立,已拥有超过 150 亿次的出行记录,为此,他们实现了很多很酷的创新技术。Go 和 Java 是通用服务器端语言,Python 和 Node 应用于特定的情况(如 Node 用于前端,Python 用于数据分析/ML)。C++被用于一些底层的库。在后端代码中使用其他语言的情况很少。


我们的 Go Monorepo 比 Linux 内核还要大,有几千名工程师在开发和维护。总而言之,很大。

Uber 是如何使用 Zig 的?

Abhinav Gupta是我们的来自 Go 平台团队的同事,其实他描述得比我好:我理解我们只是在使用 Zig 的 C 工具链,而不是将其作为语言使用。Zig 支持基于 C 的代码的交叉编译,能减少对系统 C 编译器的依赖。


Uber 技术栈发展历程

2018 年之前,Uber 的 Go 服务都有单独的存储库。2018 年,我们开始将这些服务大规模地迁移到 Go Monorepo。我的团队参与了第一波迁移——我仍然记得那有多复杂。

2019:寻求一个封闭式的工具链

当时,Go Monorepo 已经使用了一个封闭式的 Go 工具链。因此,用于构建 Go Monorepo 的 Go 编译器不会受系统上安装的编译器影响(如果有的话)。因此,无论在哪个环境下构建,都会使用相同版本的 Go。Bazel文档对此做了很好的解释


创建于 2019 年,没有太多变动。


C++工具链是一个编译 C/C++代码的程序集。不可避免地,我们的一些 Go 代码要使用CGo,所以它需要一个 C/C++编译器。然后,CGo 将 Go 和 C 部分链接成最终的可执行文件。


从 Go Monorepo 创建伊始,C++工具链就不是封闭式的:Bazel 会使用它在系统上发现的任何东西。也就是说,在 macOS 上使用 Clang,在 Linux 上使用 GCC(无论什么版本)。在 Bazel 中创建一个封闭式的 C++工具链是一项很大的工作(对于我们的 Go Monorepo 来说,需要花费数月时间),没有迫切的需求,也没有足够的痛苦,我们还无法接受做这样一件事。


现在,我们看下非封闭式 C++工具链的局限性:


  • 不能交叉编译。所以,如果需要 CGo 的话(我们的许多服务都需要),我们就无法在 Mac 上编译生成 Linux 上的可执行文件。这可以通过......而不是交叉编译来解决。

  • CGo 的可执行文件将链接到系统上发现的 glibc 版本。也就是说,在升级操作系统时(数月的努力),构建机群必须最后升级。否则,如果构建主机上 glibc 的版本比生产主机上的新,那么生成的二进制文件将链接到较新的 glibc 版本,就会与生产主机上的旧版本不兼容。

  • 我们无法使用新的编译器,即使它提供了更好的优化功能,因为我们在构建机群上运行的是旧版本的操作系统(只向后迁移编译器,而不迁移 glibc,本身就有风险)。

  • Go 的新版本的官方二进制文件在构建时使用的 GCC 版本,比我们的一些构建机器上的新。在这些机器上,我们不得不通过从源代码编译 Go 来解决这个问题。所有这些问题都很令人恼火,但不足以让我们在工具链上进行投资。

2020 年 12 月:需要 musl

我在做一个与 Uber 无关的小项目。该项目是用 Bazel 构建的,并使用了 CGo。我希望我的二进制文件是静态的,但 Bazel 并没有让这个过程变得简单。我花了几个晚上基于musl.cc创建了一个 Bazel 工具链,但没走多远,因为当时我无法深入理解 Bazel 的工具链文档,而且也没有找到一个好的示例可以参照。

2021 年 1 月:发现zig cc

2021 年 1 月,我发现了 Andrew Kelley 的博文“zig cc:一个功能强大的GCC/Clang替代品”。我推荐你读一下这篇文章;它改变了我对编译器的看法(也有助于你更好地理解本文接下来的内容,因为我是讲给 Zig 追随者听的)。综观 Andrew 的文章,zig cc有以下优势:


  • 完全封闭的 C/C++编译器,压缩包只有大约 40MB。这比 Clang 的标准发行版要小一个数量级。

  • 可以链接到通过命令行参数提供的 glibc 版本(例如,-target x86_64-linux-gnu.2.28将以 x86_64 Linux 为编译目标并链接到 glibc 2.28)。

  • 主机和目标平台是解耦的。不管是什么主机,针对目标平台linux-aarch64darwin-x86_64的设置都是一样的。

  • 与 musl 链接“只是一个不同的 libc 版本”:-target x86_64-linux-musl。我开始摆弄zig cc。我随便编译了一些程序,报告了一些问题。我想过把它做成一个bazel工具链,但有很多拦路的 bug 或缺失的功能。其中之一就是缺少 Bazel 所依赖的zig ar

2021 年 2 月:请求关注

向Zig报告了Bug。一个星期都没有动静。我每月捐 50 美元,希望“Zig 的人”能优先处理我所报告的问题。接着又是一个星期的沉默。然后我在#zig:libera.chat中扔了一枚炸弹:


<motiejus> 捐赠后,有什么规约可以用来“申请”开发时间吗?<andrewrk> ZSF只接受不附带任何条款的捐赠。<andrewrk> 你是从哪里获得了不同的印象吗?
复制代码


当时,我希望无论谁注意到这段对话都立即忘掉它。好吧,一年多以后,我又把这段话写在这里,看着玩吧。

2021 年 6 月:bazel-zig-cc 和 Uber 的 Go Monorepo

2021 年 6 月,Adam Bouhenguel创建了一个可以工作的bazel-zig-cc原型。基本功能没问题,但仍然缺少一些特性。后来,Andrew 实现了zig ar,这是一个真正可用的 bazel-zig-cc 所缺少的最后一块拼图。我集成了zig ar,完善了文档,并在Zig邮件列表中宣布了我创建的 bazel-zig-cc 分叉。至此,它对我的小项目是有效的。


在公告发布几周后,我为 Uber 的 Go Monorepo 创建了一个“WIP DIFF”:只是按照我的上线说明,天真地将其提交到我们的 CI。几乎所有的测试它都没有通过。


将 bazel-zig-cc 加入 Uber 的 Go Monorepo。


大部分失败都是由系统库依赖导致的。关于这一点,很明显,要想真正搭载 bazel-zig-cc 并编译所有的 C/C++代码,需要巨大的投入来消除对系统库的依赖,并偿还大量的技术债务。

2021 年底:回顾

  • Uber 有很多地方可以从一个封闭式的 C++交叉编译器中受益,但由于需要大量的投资,再加上没有足够的理由,所以没有获得资助。

  • bazel-zig-cc 有点用,但 bazel-zig-cc 和 zig cc 已知都存在 Bug。

  • 我无法实现必要的修改或 Bug 修复。所以,我试图实现zig ar,LLVM ar的一个小前端,但失败了。

  • 如果确定一个问题是 Zig 的问题,那么我们就无法预知它什么时候能引起 Zig 开发者的关注。有些问题几天内就得到了解决,有些则花了 6 个多月的时间,而捐款并不会改变zig cc的优先级。

  • Monorepo 上线差异(monorepo-onboarding diff)正在酝酿之中,等待时机。

2021 年底:Uber 需要交叉编译

我的任务是为 Uber 评估 arm64。撇开评估细节不说,我需要为 linux-arm64 编译软件。大量的软件!由于我们大部分的底层基础设施都在 Go Monorepo 中,我首先需要一个交叉编译器。


我终于有了一个实现交叉编译器的商业理由。现在,时间和金钱都可以投入了。“WIP DIFF”搭配zig cc是一个良好的开始,但距离终点还很远:团队不相信这是正确的事情,DIFF 更多的是一个原型。而且,要让 zig-cc 和 bazel-zig-cc 在任何情况下都可以使用,还有很多工作要做。


在一个大公司里引进这样的技术时,最重要的是风险管理。由于 Zig 是一项新技术(甚至连 1.0 都没有!),建议用它来编译我们所有的 C 和 C++代码很不寻常。我们应该做好至少十年内都使用它的计划。人们提了一些问题,并针对这些问题做了认真仔细的评估。为此,我真心感谢 Go Monorepo 团队,特别是 Ken Micklas,为这个未经证实的原型做了大量的工作和研究。

评估不同的编译器

我们需要一个交叉编译器,摆在我们面前的选项有两个:


  • grailbio/bazel-toolchain:使用普通的 Clang。没有风险。容易理解。显然,这是一个安全、恰当的解决方案。

  • ~motiejus/bazel-zig-cc:使用zig cc。有问题,有风险,不安全,不确定,没有人使用,但却是一个相当诱人的解决方案。与bazel-toolchain相比,zig cc额外提供了以下特性:

  • 可配置的 glibc 版本。使用grailbio,就需要 sysroot(本质上是一个带有系统库的 chroot,因此,程序可以与之链接),而这需要维护。

  • a working, albeit still buggy, hermetic (cross-)compiler for macOS.一个存在缺陷,但可以工作的封闭式 macOS(交叉)编译器。使用这两种方法中的任何一种处理 glibc 都没问题,然而,grailbio似乎不大可能编译到 macOS 上,更不用说交叉编译了。依赖开发者笔记本电脑上的系统编译器是不可取的,Go 平台团队亲身感受到了这一点,尤其是在 macOS 升级期间。


对于以 macOS 为目标封闭式工具链,选择的天平偏向了zig cc,连同它所有的缺陷、风险和不稳定性。


还有一个问题需要注意:我们知道,如果我们在重要的地方使用 Zig,会遇到问题,但又可能不具备解决这些问题的专业知识。作为一家大公司,我们该如何降低采用风险,确保严重的 Bug 及时得到处理?我们确信,ZSF 的出发点是好的:显然,如果我们发现并报告一个合理的 Bug,它就会得到修复。但是,怎么才能设定一个等待时间的上限呢?

金钱

50 美元的捐款无济于事,也许一份大的服务合同会有帮助?我四处打听,是否可以通过花些钱来降低“交叉编译器”的风险。获得管理层同意大约需要 10 分钟;起草、审批和签署合同大约需要 2 个月。


合同条款大致如下:


  • Uber 将问题报告到 github.com/ziglang/zig,并通知 Loris。

  • Loris 将其分配给 ZSF 的某人。

  • 解决问题。

  • 完成后,Loris 输入解决这个问题花费的时间。Uber 有权占用 ZSF 成员的时间。我们对 Zig 没有任何决定权或投票权。我们有权提出建议,但这些建议将和来自其他第三方的建议等量齐观。我们不能要求特殊权利,这在合同中有明确规定,我们也不希望那样。


合同签署了,电汇完成了,在 2022 年 1 月:


  • 我们与 ZSF 签订了服务合同,他们承诺优先处理我们提交的问题。

  • Go 平台团队承诺为我们的 C++工具链实现交叉编译和封闭。

合同金额是公开的,因为 ZSF 是非营利的。

2022 年及以后

2022 年 2 月,该工具链通过一个命令行标志(--config=hermetic-cc)做了限定。自此,你可以在 Uber 的 Go Monorepo 中调用zig cc了,不需要自定义补丁。


证明我们的提交队列登录了我的 WIP DIFF。


2022 年截至目前的时间线:


  • 今年 4 月,我在米兰演讲期间,我们向生产环境交付了第一个用 zigc-cc 编译的 Debian 软件包。

  • 今年 5 月,我们在所有的 Debian 软件包中启用了zig cc

  • 下半年,我们希望用zig cc编译所有的 cgo 代码,并将--config= hermatic -cc作为默认设置。

  • 下半年,我们希望将bazel- zigc -cc移到 github.com/uber 下。我们已经向 Zig 提交了一些问题,截至发稿时,所有的问题都已解决。有些是由 ZSF 单独处理的,有些则涉及面比较广,需要 ZSF、Uber 和 Go 开发者之间合作。

小结

我开始准备演讲,希望能给出一个大公司如何采用 Zig 的“运行手册”。然而,其实并没有什么“运行手册”;我为采用 zig-cc 所做的努力本可能会因为很多很多原因而失败。


回顾过去,我觉得要想获得成功,最重要的是在适当的时候有一个杀手锏特性。在我们的例子中,有两个:无需 sysroot 的 glibc 版本选择和交叉编译到 macOS。


原文链接:https://jakstys.lt/2022/how-uber-uses-zig/

2022 年 6 月 06 日 12:282812

评论 2 条评论

发布
用户头像
"交叉变异",好恐怖 😱
2022 年 06 月 13 日 09:05
回复
“编译”~哈哈,那处已修正
2022 年 06 月 14 日 10:44
回复
没有更多了
发现更多内容

Http详解

android 程序员 移动开发

Java的Url编码和解码

android 程序员 移动开发

Jetpack Compose 1

android 程序员 移动开发

JETPACK-COMPOSE-ALPHA-版现已发布!(1)

android 程序员 移动开发

Google Pay支付遇到的问题

android 程序员 移动开发

Java 创建型模式:单态模式,原型模式,工厂方法

android 程序员 移动开发

Jaxb2 实现JavaBean与xml互转

android 程序员 移动开发

HashMap及HashTable源码解析

android 程序员 移动开发

HTTPS详解

android 程序员 移动开发

Jetpack-在数据变化时如何优雅更新Views数据

android 程序员 移动开发

JAVA-Android-多线程实现方式及并发与同步

android 程序员 移动开发

Gson用户指南

android 程序员 移动开发

HTTPS工作原理以及Android中如何防止抓包

android 程序员 移动开发

IOS开发之——CABasicAnimation(95)

android 程序员 移动开发

Java线程(十):CAS

android 程序员 移动开发

Github标星3-2K-2020BATJ数据结构与算法笔试题及其答案吐血整理!

android 程序员 移动开发

hook(1)入门篇

android 程序员 移动开发

Http 状态码详解

android 程序员 移动开发

Glide的简单封装GlideUtils

android 程序员 移动开发

Java之JNI初步认识

android 程序员 移动开发

Jetpack系列(一) — Navigation

android 程序员 移动开发

IOS开发之——事件处理-hiTest(69)

android 程序员 移动开发

Java 网络:InetAddress类的应用以及通过Socket实现TCP编程

android 程序员 移动开发

JETPACK-COMPOSE-ALPHA-版现已发布!

android 程序员 移动开发

google vr 入门之制作简易的VR播放器(二)

android 程序员 移动开发

hencoder学习自定义view(1)

android 程序员 移动开发

IOC架构设计之Dagger2架构设计(三)

android 程序员 移动开发

JetPack现在都成了Android开发必备技能嘛?

android 程序员 移动开发

GridLayoutManager这么用,你可能还真没尝试过

android 程序员 移动开发

HashMap源码分析 —— 一篇文章搞定HashMap面试

android 程序员 移动开发

Fabric.js 从入门到________

德育处主任

大前端 可视化 canvas 画布 FabricJS

连1.0版本都没有,Uber为什么会采用这样一项新技术?_语言 & 开发_Motiejus Jakštys_InfoQ精选文章