ROG 技术创新和业务落地:基于 Rust 的高性能 Go 编译器

合作是技术创新的关键,Go 与 Rust 的跨界融合是优秀实践。多数团队在学习 Rust 时有难度,将大量 Go 代码转为 Rust 耗时费力且容易引发技术问题。

字节跳动在业务实践中也遇到了这个难题:大量 Go 服务在核心场景出现性能瓶颈,但重构难度大。此时,从编译器入手,在不改动代码、不增成本的前提下提升性能,而ROG 正是专注于高性能的 Go 与 Rust 跨界编译器。

本文根据字节跳动服务框架团队研发工程师陈卓钰在 CloudWeGo 四周年技术沙龙上的演讲内容整理而成,详细解读了 ROG 的设计思路、核心实现以及在业务中的实际应用效果。

点击链接可查看本次分享回放👉🏻b23.tv/YnSyAZy

一、业务痛点:当 Go 的性能成为瓶颈

字节跳动有大量基于 Go 开发的服务,其中不少还是核心业务,Go 服务在整体服务中占比超过一半。但在一些计算量很大的场景下,Go 语言逐渐暴露出性能不足的问题,无法充分发挥现代 CPU 的计算能力,导致资源没有得到最优利用。 Go 语言更注重编译速度和开发效率,但在追求更高性能和更低成本的背景下,它编译后的代码执行效率成了"降本增效"的一大阻碍。之前,大部分优化工作都集中在技术架构和业务逻辑方面,而服务框架团队选择从编译器本身入手,在不改动任何业务代码的情况下,为庞大的 Go 服务体系提升性能。 对于 ROG 这类复杂项目,明确目标与非目标至关重要:

  • 核心目标: 与 Go 语言特性完全兼容,保证任何合法 Go 程序在 ROG 中正常运行且结果一致;打造高性能运行环境;支持 LLVM 的高级优化功能如 LTO、PGO、BOLT 等;构建可扩展的编译器架构。

  • 非目标: 暂时不支持自举;不追求编译速度,开发阶段可用标准 Go 编译器提效,生产阶段切换 ROG 追求性能;暂时不兼容 Plan9 汇编和 linkname 内部符号,底层代码重写导致难以保证一致且兼容性实现难度大。

二、技术挑战:在 LLVM 上重构带 GC 的编译型语言

把 Go 和业界最成熟的编译器后端 LLVM 结合起来,并不是第一次尝试。但是,像版本老旧、没人维护的 gccgo,或者应用场景有限、功能不完整的 TinyGo,这些现有的方案都不能满足字节跳动大规模、高性能的业务需求。 因此,服务框架团队决定自己开发一款基于 Rust 和 LLVM 的全新 Go 编译器 ------ ROG。最大的挑战在于,LLVM 最初是为 C++ 这类没有 GC 功能的语言设计的,在处理像 Go 这种"编译型 + 带 GC"的语言时,会遇到三个主要问题:

  • GC 指针追踪:如何在 LLVM IR 层面准确地插入写屏障,保证 GC 能够追踪到所有指针,避免出现内存泄漏。

  • 栈动态扩缩容:Go 协程的栈空间会根据需要动态变化,在函数调用时需要插入额外的栈检查代码,而 LLVM 的标准流程里没有对这个功能的原生支持。

  • 抢占式调度:为了避免信号带来的复杂异步问题,ROG 需要设计一套基于 Checkpoint 的确定性调度机制,在函数开始时进行检查,防止某个任务长时间占用调度器。

三、方案设计:高性能、可扩展的 ROG 编译器

ROG 的主要目标是在完全兼容 Go 语言特性的基础上,打造一款高性能、可扩展的编译器。它由前端、中间优化流程和基于 Rust 的运行环境组成,最后通过 LLVM 后端生成经过高度优化的机器码。ROG 包含多个核心组件,如内存分配器、垃圾回收器(GC)等。

(一)内存分配器:分级管理 + 无锁优化,解决主流方案痛点
  • 现有内存分配器的问题:团队研究了 jemalloc、TCMalloc、bdw - gc 等几种主流的内存分配方案,发现都不能完全满足需求。
  • ROG 分级内存管理方案设计:针对以上问题,团队设计了专门的方案,主要优点包括:支持最大 1TB 的堆内存,能够在运行时对对象进行索引,支持批量释放,并且缓存的局部性很好。

  • 核心分配逻辑

    1. 优先从当前线程的空闲内存块中获取内存,在频繁分配内存的场景下不需要加锁;

    2. 如果要分配的对象超过了一定大小,就会触发内存扩容,使用新的内存块来存储;

    3. 物理内存以操作系统的页面为基本单位,多个页面组合成 Chunk 进行统一管理;

    4. P 和线程是一一对应的,线程操作 P 上的内存资源是线程私有的;只有在操作全局的 TLSFAlloc 和 BuddyAlloc 时才需要进行同步,这样能保证高性能。

(二)垃圾回收器:多策略支持 + 高效流程,兼顾兼容性与性能
  • 支持的 GC 策略:ROG 的垃圾回收器支持 STW GC、三色标记 GC 等多种算法,目前默认使用 STW GC。

  • 核心工作流程

    1. 标记准备:暂停所有的 P(逻辑处理器)和 G(协程),为标记阶段做准备;

    2. 并发标记:从 GCRoots 开始,多个线程同时标记存活的对象,把当前对象引用的未标记对象加入队列,直到所有能访问到的对象都被标记;

    3. 终止标记:因为 Go 支持 Finalizer 特性,有些对象需要"复活",由单线程处理复活逻辑,同时复活相关对象及其引用的所有对象;

    4. 并发清理:直接清理没有被标记的对象,把标记为存活的对象作为下一次 GC 的初始对象,调用析构函数完成整个 GC 流程。

四、落地成效:CPU 显著下降,QPS 提升 10%+

ROG 在实际业务场景中的应用取得了明显的性能提升。

  • 计算密集型场景 :对于纯计算的多线程程序,用原生 Go 编译需要 12.99 秒,用 ROG 编译后,时间缩短到了 2.91 秒 ,性能提升了 346%

  • 微服务框架场景 :在 Kitex 框架的性能测试中,用 ROG 编译的版本和原生 Go 相比,每秒请求数从 330051 提高到了 364808 ,提升了 10.5%

  • 线上业务表现

    • 服务 A:平均 CPU 使用率从 54.5% 降到了 45% ,降低了 9.5%

    • 服务 B:平均 CPU 使用率从 36% 降到了 22% ,降低了 14%

更重要的是,由于 ROG 的运行环境是用 Rust 开发的,天生就支持 Go 和 Rust 的混合编译和链接。这意味着开发者可以直接在 Go 代码中调用高性能的 Rust 或 C 函数,不用再通过 CGo 进行复杂的封装,为业务的进一步优化提供了新的可能。

五、未来展望:迈向更深度的跨语言融合

目前,ROG 已经证明了它在性能优化方面的价值,但这只是个开端。服务框架团队的目标不仅仅是让 Go 代码运行得更快,还希望探索两种语言生态的深度融合;理想情况下,希望未来的 ROG 不只是一个编译器,还能成为连接 Go 和 Rust 的桥梁。

未来,会重点支持 LLVM 的更多高级优化功能,比如 PGO 和 LTO,进一步挖掘性能潜力,同时也会不断优化 ROG 的运行环境,尝试在更多核心业务场景中应用,为开发者提供一个无缝、高效的跨语言开发体验。

相关推荐
腾讯云中间件20 小时前
腾讯云 RocketMQ 5.x:如何兼容 Remoting 全系列客户端
架构·消息队列·rocketmq
代码AI弗森20 小时前
构建超级个体:AI Agent核心架构与落地实践全景解析
人工智能·架构
檐下翻书17321 小时前
互联网企业组织结构图在线设计 扁平化架构模板
论文阅读·人工智能·信息可视化·架构·流程图·论文笔记
CinzWS21 小时前
基于Cortex-M3的PMU架构--关键设计点
架构·pmu
白帽子黑客罗哥21 小时前
AI与零信任架构协同构建下一代智能防御体系
人工智能·架构
早睡的叶子21 小时前
VM / IREE 的调度器架构
linux·运维·架构
狗哥哥21 小时前
Vue 3 页面缓存机制深度实践:从原理到落地
前端·vue.js·架构
狗哥哥1 天前
Vite 插件开发实战:从业务痛点到优雅解决方案
vue.js·架构·前端框架
一水鉴天1 天前
整体设计 定稿 备忘录仪表盘方案 之2 应用 : “整体设计” 概念图的完整方案 初稿 之2 (豆包助手 )
人工智能·架构·状态模式
winfield8211 天前
如何保证服务高可靠?
架构