Restartable Sequences
AI 深度解读
Restartable Sequences:解锁多核时代无锁编程的终极秘密
背景
在系统编程的前沿领域,有一个长期被低估的概念:可重启序列(Restartable Sequences,简称 rseq)。这一概念最早于 2018 年左右随 Linux 4.18 内核引入,旨在解决现代多核处理器扩展性带来的挑战。
随着微处理器核心数量的激增(从几十核发展到如今的 128 核甚至 192 核),传统的基于锁(Locks)或原子操作(Atomics)的数据结构已难以满足性能需求。虽然目前 rseq 主要依赖手写汇编代码在 Linux 上使用,但作者坚信,未来所有操作系统都将支持 rseq() 系统调用,所有系统编程语言都将重新设计以表达可重启序列,所有数据结构库也将重写以利用这一特性。
目前,已知使用 rseq 的软件包括 tcmalloc、jemalloc、glibc 以及作者开发的 cosmopolitan C 运行时。随着廉价的高核心数 CPU(如 128 核或 192 核) becoming available,这一技术将从边缘走向主流。
核心内容
1. 性能实测:核心数量决定优化价值
作者通过对比不同硬件平台上的 malloc() 实现,展示了 rseq 带来的惊人性能提升。关键在于,核心数越多,rseq 的优势越明显:
- Raspberry Pi 5 (4 核, $160):使用 rseq 使
malloc()实现比传统的dlmallocmspace 方案快 3 倍。对于大多数开发者而言,这是一个“有则更好”的提升。 - System76 Thelio Astra (128 核 Ampere Altra, $4,834):使用 rseq 使
cosmopolitan malloc()比基于sched_getcpu() % 32的分片(sharding)操作快 34 倍。 - AMD Threadripper Pro 7995WX (96 核, $17,628.55):使用 rseq 使
malloc()比相同的sched_getcpu()互斥分片技术快 43 倍。
作者指出,缺乏此类高核心数工作站的系统程序员可能会像“恐龙”一样被时代抛弃,错失 10 倍性能优化的低垂果实。他个人曾因购买 96 核 CPU 而陷入财务困境,但这一投资带来了巨大的回报:其矩阵乘法优化工作获得了媒体关注,在 AI 社区成名,帮助项目被 32% 的组织采用,并为他赢得了 Google 的录用通知(用于改进 Gemini 的 TPU 性能)。
2. rseq 的工作原理
rseq 的核心机制在于内核与用户态线程之间的协作,主要分为两个阶段:
阶段一:获取 CPU 编号(TLS 内存更新)
当 Cosmopolitan C 运行时在 Linux 上创建线程时,会调用 rseq() 系统调用,内核为此分配 32 字节的线程本地存储(TLS)内存。在后续线程的生命周期中,每当线程被重新调度(rescheduled)时,内核会自动更新该 TLS 内存中的 CPU 编号。
- 优势:获取 CPU 编号仅需一条 1 纳秒的
relaxed mov指令,而传统的getcpu()系统调用需要等待约 1 微秒。
阶段二:可重启序列(rseq_cs 字段)
rseq TLS 内存中还有一个 rseq_cs 字段。通常它为 NULL,但可以被更新为一个指向程序中一段汇编指令序列的指针。
- 机制:当内核抢占线程并尝试将其迁移到不同 CPU 时,如果检测到
rseq_cs非空,它会检查程序计数器(x86 架构下为%rip)是否位于指定的指令区间内。 - 中断处理:如果是,内核会强制线程跳转到用户指定的中止处理程序(abort handler)。该处理程序可以执行诸如跳回函数开头以重试操作等动作。
3. 从原子操作到分片,再到无锁优化
作者通过一个链表(List)的 push 和 pop 操作案例,阐述了为何需要 rseq:
方案 A:纯原子操作(Atomic)
使用 atomic_compare_exchange_weak_explicit 处理 ABA 问题。
- 缺点:虽然逻辑简单,但在多核环境下,多个核心共享同一缓存行(cacheline,64 字节)会导致 CPU 内部产生类似互斥锁的竞争。这种硬件层面的竞争往往比用户态实现的锁更糟糕,导致性能低下。
方案 B:分片互斥锁(Sharded Mutex)
将数据结构分片,每个 CPU 拥有独立的列表区域,通过 sched_getcpu() 索引。
- 代码结构:
static struct { alignas(64) pthread_mutex_t lock; struct List *list; } gil[CPU_SETSIZE]; - 优势:每个 CPU 拥有独立的互斥锁,只有在极端情况下才会发生争用。未争用的锁操作成本约为 15 纳秒,而争用的锁操作成本至少为 200 纳秒。
alignas(64)确保每个指针位于独立的缓存行,进一步减少硬件争用。 - 瓶颈:尽管比纯原子操作快,但 15 纳秒的锁开销对于简单的链表操作来说仍然巨大(线程本地链表操作仅需约 1 纳秒)。
方案 C:rseq 无锁优化 为了消除那 15 纳秒的开销,必须移除互斥锁。唯一的障碍是操作系统可能在执行几个汇编指令的微小序列期间中断线程,导致数据不一致。
- rseq 的解决方案:利用 rseq 的中断处理机制。如果线程在执行关键序列时被抢占,内核会触发中止处理程序,让线程从头重试。这使得我们可以实现真正的无锁、无原子操作的高性能数据结构。
其他替代方案的局限性
- 实时操作系统(RTOS):保证线程不被抢占,但适用范围有限。
- 调度策略(sched_setscheduler):提供部分控制权,但不够通用。
- CPU 亲和性(sched_setaffinity):支持 Linux、FreeBSD 和 Windows,允许将线程绑定到特定 CPU。但这要求应用程序管理线程绑定,且并非所有场景都适用。
关键要点
- rseq 是系统编程的未来:它是解决多核扩展性问题的关键,未来将成为操作系统、编程语言和标准库的标准配置。
- 性能提升显著:在高核心数(>32核)机器上,rseq 相比传统的分片互斥锁方案可实现 30-40 倍的性能提升。
- 机制核心:
- 内核自动维护线程的 CPU 编号(通过 TLS,成本极低)。
- 内核监控用户态关键代码段,若发生迁移则强制跳转至中止处理程序进行重试。
- 传统方法的局限:
- 纯原子操作在多核共享缓存行时性能急剧下降。
- 分片互斥锁虽优于纯原子操作,但仍有 15ns 级别的锁开销。
- 硬件门槛:要充分发挥 rseq 的优势,需要拥有高核心数 CPU(如 96 核或 128 核)的工作站。
- 应用场景:特别适用于高频、低延迟的数据结构操作,如内存分配器(malloc)、链表操作等。
意义与影响
rseq 的引入标志着系统编程范式的一次重要转变。它允许开发者在无需锁或原子操作的情况下创建线程安全的数据结构,从而充分利用现代微处理器的核心扩展能力。
对于系统程序员而言,掌握 rseq 技术将成为区分“恐龙”与“现代开发者”的关键。随着高核心数 CPU 价格的下降,这一技术将从高端服务器领域迅速普及到普通工作站。
此外,rseq
