您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

使用Zig在arm64上引导Uber的基础设施

时间:2023-06-16 14:15:19  来源:InfoQ  作者:

作者丨 Uber 工程播客

译者丨明知山

策划丨 Tina

简 述

2021 年 11 月,我们决定评估 arm64 架构在 Uber 的可行性。我们的大多数服务是用 Go 或 JAVA 编写的,但我们的构建系统只能编译成 x86_64。现在,得益于开源合作,Uber 拥有了一个独立于系统的构建工具链,可以无缝地支持多种架构。我们使用这个工具链来引导 arm64 主机。本文将分享我们是如何着手去做这件事情的,以及我们早期的想法、遇到的问题、达成的一些成就和未来的方向。

我们从 2021 年 11 月开始使用专门的 linux/x86_64 基础架构,而到了 2023 年 1 月,我们有:

  • 用于生产环境服务器架构(x86_64 和 arm64)的 C++ 工具链,由 zig cc 提供支持;
  • 一些在 arm64 硬件上运行的核心基础设施服务,为未来的扩展提供了可能性。

让我们来看看我们是如何做到的。

为什么要考虑 arm64 架构

所有的主流云供应商都在 arm64 上投入巨资,再加上 arm64 与古老的 x86_64 相比所表现出来的平台优势(能耗、价格、计算性能),我们觉得很有必要认真考虑让 arm64 成为我们平台的一部分。

于是,我们开始尝试自己去探究。我们的第一个目标如下所述:

在 arm64 架构上运行一个大型的应用程序,并对可能节省的成本进行度量。

其中一个关键点是最小化运行和基准测试消耗多个核心的服务所需的工作量。我们找到了两种截然不同的方法:

  • 在并行区域或现有区域中的独立集群提供基本的 arm64 支持,并在那里运行测试(实验质量);
  • 让所有的核心基础设施都知道现在不止一种架构,然后像生成其他 SKU 一样生成 arm64 主机并测试应用程序。

考虑到最小化工作量是我们优先考虑的事项,所以第一个选项似乎更适合我们。毕竟,我们为什么要把时间和金钱投入到有可能被放弃的东西上呢?我们考虑运行一个“并行区域”,它具备 arm64 架构,但在其他方面与生产环境是分离的(并且质量要求更为宽松,方便我们快速前进)。

不久之后,我们有了一个更重要的支持 arm64 的理由:如果我们可以在 arm64 上运行工作负载,就可以让平台的能力多样化,从而让自己处于一个更有利的位置。于是,我们的使命变成了(直到今天仍是如此):

通过在 arm64 上部署一些生产应用程序来降低 Uber 的计算成本、增加容量多样性,以及使我们的平台现代

我们最初是带着原型思维开始的,但现在却有了 180 度转变,形成了一个指导原则:

没有 hack,所有的内容都在主线上(也就是说,没有长期的分支或补丁)。

既然我们的核心基础设施需要提供一流的 arm64 支持,那么这个项目就很自然地被分成两个部分:

  1. 第一个任务是将包含了我们几乎所有基础架构代码的 Go 代码库编译成 arm64 二进制文件;
  2. 修改与构建、存储、下载和执行代码相关的所有东西(构建主机、工件存储和调度器),让它们知道现在存在两种架构。

那么如何编译成 arm64 二进制文件?当然是直接在 arm64 主机上进行原生构建,或者通过交叉编译。我们有必要先来了解一下原生编译和交叉编译的差异和要求。

原生编译和交叉编译的基础知识

一些我们可能不太熟悉的术语:

  • 二进制文件 是由源代码编译而来的机器代码程序。
  • 工具链 是将源代码编译为二进制文件所需的一组工具,通常包括预处理器、编译器、链接器等。
  • 密闭(hermetic)工具链 是指无论在什么样的环境下,只要给定相同的输入,总是产生相同输出的工具链。这里的“密闭”是指它不使用来自主机的文件,并且包含编译文件所需的所有东西。
  • 主机 (host)是指编译二进制文件的机器。
  • 目标平台 (target)是指运行二进制文件的机器。
  • 在进行 原生编译 时,主机和目标是相同的平台(即操作系统、处理器架构和共享库是相同的)。
  • 在进行 交叉编译 时,主机和目标是不同的平台(例如,从 macOS arm64 (M1) 编译成 x86_64 Linux)。有时候,目标机器可能无法编译代码,但可以运行。例如,一块智能手表可以运行已编译的代码,但不能运行编译器,因此我们可以使用交叉编译器为手表编译程序。
  • sysroot 是目标平台文件系统的归档。例如,特定于目标平台的头文件、共享库、静态库。通常是交叉编译工具链所必需的,下面将会讨论。
  • aarch64 或 arm64 是指处理器架构。

下图显示了如何通过原生编译(左)和交叉编译(右)将源文件 mAIn.c 编译成可执行文件。

图 1:输入文件 main.c 原生编译(左)或交叉编译(右)为 aarch64 架构。

原生编译只需要较少的配置和准备工作就可以使用,因为这是大多数编译器工具链的默认模式。从表面上看,我们可以在云供应商的平台上启动一些 arm64 虚拟机,并从那里开始引导我们的工具。但是,我们所有的服务器都使用相同的基础镜像,包括构建主机。基础镜像包含许多从 Go 代码库编译出来的内部工具。因此,我们遇到了一个先有鸡还是先有蛋的问题:如何为我们的第一个 arm64 构建主机编译工具?

示例:使用 GCC 和 Clang 进行交叉编译

让我们在 x86_64 Linux 主机上编译一个 C 文件,目标平台是 Linux aarch64:

GCC 调用目标平台特定的可执行文件(aarch64-linux-gnu-gcc),而 Clang 接受目标平台作为命令行参数(-target <…>):

表面上看,用 GCC 和 Clang 交叉编译 C 源文件似乎很容易,但背后都发生了什么?

基于 LLVM 的 C/C++ 工具链

“clang”使用哪些文件来构建最终的可执行文件?我们来跟踪一下:

以下是这些相关的文件:

  • (没有显示出来的)工具:C 编译器(Clang)和链接器(ld)。
  • /usr/aarch64-linux-gnu/include 中的头文件。这些通常是 GNU C 库头文件。有些程序使用 Linux 内核的公共头文件,但本例中没有。头文件是特定于目标平台的。
  • 编译的、特定于目标架构的库:
    • 动态链接器 /usr/aarch64-linux-gnu/lib/ld-linux-aarch64.so.1;
    • C 库,共享对象:/usr/aarch64-linux-gnu/lib/libc.so.6;
    • 程序加载器: crt .o。
  • 其他库:libgcc 和 libc_nonshared。

现在我们已经知道交叉编译器使用了哪些东西,我们可以将依赖项分为两类:

  • 特定于主机的工具(编译器、链接器和其他与目标平台无关的程序);
  • 特定于目标平台的库和头文件,它们是为目标平台编译最终程序所必需的。

Uber 需要支持以下这些目标平台:

  • Linux x86_64(带有 glibc 2.28);
  • Linux x86_64(带有 glibc 2.31);
  • Linux x86_64(带有 musl);
  • Linux arm64(aarch64,带有 glibc 2.31);
  • Linux arm64 (aarch64,带有 musl)。

在撰写本文时,GCC 和 LLVM 都不能交叉编译 macOS 二进制文件。因此,我们维护了一个专门的构建集群来编译 macOS 目标平台。交叉编译 macOS 目标平台是非常有必要的,但我们目前还没有做到这一点。

以下是我们目前支持的主机平台:

  • Linux x86_64:构建集群、DevPod 和开发者笔记本电脑;
  • macOS x86_64:老一代 macOS 开发者笔记本电脑;
  • macOS aarch64(Apple Silicon):新一代 macOS 开发者笔记本电脑。

下图画出了主机工具链、sysroot 以及它们之间的关系,每个主机工具链(左)都可以使用任意特定于目标平台的 sysroot(右):

图 2:基于 LLVM 的工具链需要每个主机和目标平台的 tarball(“sysroot”)

为了支持这些主机和目标平台,我们需要维护 8 个压缩文件:3 个工具链(每个主机架构需要一个编译的 LLVM)和 5 个目标平台的 sysroot。一个典型的 LLVM 工具链需要 500 到 700MB(压缩包),一个典型的 sysroot 需要 100 到 150MB(压缩包)。在编译代码之前,加上其他工具,总共需要下载和解压约 1.5GB 的压缩文件。Linux x86_64 的 Go 1.20 工具链压缩包为 95MB,是编译代码所需的最大的下载文件。

为了完整起见,我们来看一下 GCC。你可能还记得之前提到 GCC 交叉编译器是 aarch64-linux-gnu-gcc,这意味着每个主机和目标平台都需要一个完整的工具链。因此,如果我们要使用基于 GCC 的工具链,就需要维护 3 5=15 个工具链。如果我们添加一个新的主机平台(例如 Linux aarch64)和两个目标平台(分别针对 x86_64 和 aarch64 的 Linux glibc.2.36),那么需要维护的压缩包数量将跃升至 4

7=28 个!

在购买 Bazel 工具链时,我们评估了 GCC 和基于 LLVM 的工具链。LLVM 更受青睐,因为它需要维护的压缩文件数量的增长是线性的(而不是 GCC 那样的二次幂增长)。但我们能做得更好吗?

Zig 工具链

Zig 采用了不同的方式:它对所有受支持的目标平台使用了相同的工具链。

它在编译时使用了哪些文件?如果我们跟踪一下会发现,它只使用了来自 Zig SDK 的文件(中间文件放在 /tmp 目录下)。主机系统没有受到任何影响,这意味着 Zig 是完全独立的。

为什么 Zig 能做到这样,而 Clang 却不能?Clang 和 Zig 之间主要的差异是什么?Zig 需要的依赖项与 Clang 一样,我们来看一下:

  • 工具:C 编译器(Clang)和链接器(lld)。
    • 它们被静态地链接到 Zig 二进制文件中,对于 macOS,Zig 实现了自己的链接器。
  • /usr/aarch64-linux-gnu/…中的头文件。
    • Zig 捆绑了多个版本的 glibc、musl libc、linux 内核和其他一些头文件,并自动包含它们。
  • 编译好的特定于目标平台的库:动态链接器、glibc(多版本)、程序加载器。
    • Zig 根据具体的平台在后台动态编译所有这些文件。
  • 其他库:libgcc 和 libc_nonshared。
    • Zig 重新实现了这些库中的函数。

因此,Zig 可以用一个工具链编译所有受支持的目标平台。为了支持我们的 3 个主机和 5 个目标平台,我们需要从 https://ziglang.org/download 下载 3 个 Zig tarball 文件:

图 3:每个主机平台需要 1 个工具链。同一工具链可以编译所有目标平台。

Zig 作者 Andrew Kelley 在他的博客中更详细地解释了 Zig 在 Clang 之上添加了哪些东西。不管我们希望支持多少个目标平台,只需要一个主机工具链,这是非常诱人的。

我们尝试做一些其他工具链无法做到的事情:在 Linux 机器上交叉编译和链接 macOS 可执行文件:

尽管在 2021 年底,Zig 还只是一项未经验证的新技术,但一个主机平台一个 tar 包和交叉编译 macOS 目标的能力赢得了团队的青睐。我们开始使用 Zig,将 zig cc 整合到我们的 Go 代码库中。

Bazel 与 Zig

对于 Bazel 来说,只有一个 C++ 工具链(在本例中是 Zig SDK)是不够的:它还需要一些粘合代码,一个工具链配置。2022 年 2 月,Go 代码库对 zig cc 的初步支持是通过添加到一个配置标志来实现的:

  •  
bazel build –config=hermetic-cc < …>

最开始所有的东西都不正常,大部分的测试都无法构建通过,更不用说执行了。我们开始慢慢解决这些问题。到 2022 年 9 月,所有测试都通过了。自 2023 年 1 月起,Zig 工具链可以将 Uber Go 代码库中的所有 C 和 C++ 代码编译到 Linux 目标平台。

Uber 自 2022 年 4 月以来一直在运行 Zig 生成的二进制文件,因此我们对 Zig 信心满满。Bazel 和 Zig 之间的粘合代码最初放在 Adam Bouhenguel 的代码库 bazel-zig-cc 中,后来被 Motiejus Jakštys 克隆并进一步开发,最终转到了 https://Github.com/uber/hermetic_cc_toolchain。

因为与 Zig 软件基金会合作,我们可以寻求对我们来说重要的解决方案。Zig 的人帮助我们发现和修复 Go 和 Zig 中的问题。因为在 2021 年合作进展顺利,Uber 决定将合作关系延长到了 2023 年和 2024 年。Zig 软件基金会所做的所有工作都是开源的,这让更大社区从中受益。

对 arm64 支持的进展

等到工具链足够成熟,可以进行 arm64 平台编译,我们就开始在内部加强对 arm64 的支持。例如:

  • 当开发人员在 Go 代码库中定义了 Docker 镜像(使用 rules_docker,它相当于 Dockerfile,只是是在 Bazel 中使用),CI 将编译 x86_64 和 arm64 的依赖代码,并且如果无法编译就不允许通过。
  • 我们将 Go 代码库中所有的 Debian 包编译到了 arm64 并发布,尽管它们中的大部分不是我们必需的。与 Docker 镜像类似,CI 确保它们可以编译到 arm64 和 x86_64。目前不可能在我们的 Go 代码库中声明一个不能编译到 arm64 的新的 Debian 包。

在能够将程序编译为 arm64 之后,我们开始采用所有可以存储、下载和执行原生二进制文件的系统。现在,我们有:

  • 开发环境中的 arm64 主机,就像其他 x86_64 主机一样;
  • 运行在 arm64 主机上的几个核心基础设施服务(例如,内部构建的容器调度器和支配程序);
  • 继续扩大 arm64 的使用和支持。

我们 2023 年的计划包括:

  • 为 arm64 增加 Kube.NETes 支持;
  • 在 Kubernetes 的 arm64 主机上运行面向客户的服务。

Uber 有使用 Zig 语言吗

可以说有,也可以说没有。例如,ermet_cc_toolchain 中的启动器是我们用 Zig 编写的。嵌入到可执行文件中的运行时库(compiler-rt)是用 Zig 编写的。总而言之,我们的大多数 Go 服务都涉及到了一点 Zig,并且是用 Zig 编写的工具链编译的。

尽管如此,我们还没有将用 Zig 编写的生产应用程序引入到我们的代码库中(虽然工具链已经完全设置好了),因为目前公司中只有少数人知道这门语言。

总 结

截止 2023 年 1 月 16 日,所有发布到生产环境的 C/C++ 代码都通过 hermect_cc_toolchain 进行编译。因为 Zig 现在是我们 Go 代码库的关键组成部分,因此 hermetic_cc_toolchain 的维护得到了财务(与 Zig 软件基金会的合作将到 2024 年底)和 Uber 员工工时的支持。

虽然可以在 arm64 硬件上运行我们的核心基础设施,但我们还没有准备好运行面向客户的应用程序。我们的下一步是在 arm64 上试验面向客户的应用程序,这样就可以测试它的性能并决定未来的方向。

原文链接:

https://www.uber.com/en-SG/blog/bootstrapping-ubers-infrastructure-on-arm64-with-zig



Tags:Zig   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
作为一名 Go 程序员,我为啥更喜欢用 Zig?
作者 | Vladimir Vivien编译 | 如烟出品 | 51CTO技术栈(微信号:blog51cto)Zig 是一种比较新的编程语言,于 2016 年首次推出。Zig 社区将其描述为“一种用于维护稳固的、可优化和...【详细内容】
2023-12-06  Search: Zig  点击:(164)  评论:(0)  加入收藏
Rust、Zig,能够干翻Java、C?
撰稿 | 云昭出品 | 51CTO技术栈(微信号:blog51cto)众所周知,由于其根源于 Mozilla,Rust 在历史上与系统编程联系在一起,它对安全性、速度和并发性的承诺,使其在基础设施层面得到了...【详细内容】
2023-11-17  Search: Zig  点击:(205)  评论:(0)  加入收藏
使用Zig在arm64上引导Uber的基础设施
作者丨 Uber 工程播客译者丨明知山策划丨 Tina简 述2021 年 11 月,我们决定评估 arm64 架构在 Uber 的可行性。我们的大多数服务是用 Go 或 Java 编写的,但我们的构建系统只能...【详细内容】
2023-06-16  Search: Zig  点击:(291)  评论:(0)  加入收藏
蓝牙,WiFi,ZigBee,谁会是最后的赢家?
曾经,在2015年极客公园创新大会上,小米首次在非官方平台发布了新款产品小米智能家庭套装。自此,Zigbee便常出现在大众视野中。如今,小米在IoT开发者平台上明确说明,不再推广Zigbe...【详细内容】
2022-04-08  Search: Zig  点击:(322)  评论:(0)  加入收藏
物联网通讯协议——Zigbee
一、什么是Zigbee ZigBee是一种新兴的短距离、低速率无线网络技术。具有成本低、体积小、能量消耗小和传输速率低等优势。 ZigBee是介于无线标识技术和蓝牙之间的一种技术...【详细内容】
2020-08-20  Search: Zig  点击:(354)  评论:(0)  加入收藏
物联网无线传输技术WIFI、蓝牙、UWB、MTC、ZigBee、NFC等技术
随着万物互联时代的到来,物与物之间的连接方式也在不断发展和更新。 如果说,传感器是物联网的触觉,那么,无线传输就是物联网的神经系统,将遍布物联网的传感器连接起来。在物联...【详细内容】
2020-05-07  Search: Zig  点击:(318)  评论:(0)  加入收藏
▌简易百科推荐
即将过时的 5 种软件开发技能!
作者 | Eran Yahav编译 | 言征出品 | 51CTO技术栈(微信号:blog51cto) 时至今日,AI编码工具已经进化到足够强大了吗?这未必好回答,但从2023 年 Stack Overflow 上的调查数据来看,44%...【详细内容】
2024-04-03    51CTO  Tags:软件开发   点击:(6)  评论:(0)  加入收藏
跳转链接代码怎么写?
在网页开发中,跳转链接是一项常见的功能。然而,对于非技术人员来说,编写跳转链接代码可能会显得有些困难。不用担心!我们可以借助外链平台来简化操作,即使没有编程经验,也能轻松实...【详细内容】
2024-03-27  蓝色天纪    Tags:跳转链接   点击:(13)  评论:(0)  加入收藏
中台亡了,问题到底出在哪里?
曾几何时,中台一度被当做“变革灵药”,嫁接在“前台作战单元”和“后台资源部门”之间,实现企业各业务线的“打通”和全域业务能力集成,提高开发和服务效率。但在中台如火如荼之...【详细内容】
2024-03-27  dbaplus社群    Tags:中台   点击:(9)  评论:(0)  加入收藏
员工写了个比删库更可怕的Bug!
想必大家都听说过删库跑路吧,我之前一直把它当一个段子来看。可万万没想到,就在昨天,我们公司的某位员工,竟然写了一个比删库更可怕的 Bug!给大家分享一下(不是公开处刑),希望朋友们...【详细内容】
2024-03-26  dbaplus社群    Tags:Bug   点击:(5)  评论:(0)  加入收藏
我们一起聊聊什么是正向代理和反向代理
从字面意思上看,代理就是代替处理的意思,一个对象有能力代替另一个对象处理某一件事。代理,这个词在我们的日常生活中也不陌生,比如在购物、旅游等场景中,我们经常会委托别人代替...【详细内容】
2024-03-26  萤火架构  微信公众号  Tags:正向代理   点击:(11)  评论:(0)  加入收藏
看一遍就理解:IO模型详解
前言大家好,我是程序员田螺。今天我们一起来学习IO模型。在本文开始前呢,先问问大家几个问题哈~什么是IO呢?什么是阻塞非阻塞IO?什么是同步异步IO?什么是IO多路复用?select/epoll...【详细内容】
2024-03-26  捡田螺的小男孩  微信公众号  Tags:IO模型   点击:(9)  评论:(0)  加入收藏
为什么都说 HashMap 是线程不安全的?
做Java开发的人,应该都用过 HashMap 这种集合。今天就和大家来聊聊,为什么 HashMap 是线程不安全的。1.HashMap 数据结构简单来说,HashMap 基于哈希表实现。它使用键的哈希码来...【详细内容】
2024-03-22  Java技术指北  微信公众号  Tags:HashMap   点击:(11)  评论:(0)  加入收藏
如何从头开始编写LoRA代码,这有一份教程
选自 lightning.ai作者:Sebastian Raschka机器之心编译编辑:陈萍作者表示:在各种有效的 LLM 微调方法中,LoRA 仍然是他的首选。LoRA(Low-Rank Adaptation)作为一种用于微调 LLM(大...【详细内容】
2024-03-21  机器之心Pro    Tags:LoRA   点击:(12)  评论:(0)  加入收藏
这样搭建日志中心,传统的ELK就扔了吧!
最近客户有个新需求,就是想查看网站的访问情况。由于网站没有做google的统计和百度的统计,所以访问情况,只能通过日志查看,通过脚本的形式给客户导出也不太实际,给客户写个简单的...【详细内容】
2024-03-20  dbaplus社群    Tags:日志   点击:(4)  评论:(0)  加入收藏
Kubernetes 究竟有没有 LTS?
从一个有趣的问题引出很多人都在关注的 Kubernetes LTS 的问题。有趣的问题2019 年,一个名为 apiserver LoopbackClient Server cert expired after 1 year[1] 的 issue 中提...【详细内容】
2024-03-15  云原生散修  微信公众号  Tags:Kubernetes   点击:(6)  评论:(0)  加入收藏
站内最新
站内热门
站内头条