合作是技术创新的关键,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 的堆内存,能够在运行时对对象进行索引,支持批量释放,并且缓存的局部性很好。
-
核心分配逻辑:
-
优先从当前线程的空闲内存块中获取内存,在频繁分配内存的场景下不需要加锁;
-
如果要分配的对象超过了一定大小,就会触发内存扩容,使用新的内存块来存储;
-
物理内存以操作系统的页面为基本单位,多个页面组合成 Chunk 进行统一管理;
-
P 和线程是一一对应的,线程操作 P 上的内存资源是线程私有的;只有在操作全局的 TLSFAlloc 和 BuddyAlloc 时才需要进行同步,这样能保证高性能。
-
(二)垃圾回收器:多策略支持 + 高效流程,兼顾兼容性与性能
-
支持的 GC 策略:ROG 的垃圾回收器支持 STW GC、三色标记 GC 等多种算法,目前默认使用 STW GC。
-
核心工作流程:
-
标记准备:暂停所有的 P(逻辑处理器)和 G(协程),为标记阶段做准备;
-
并发标记:从 GCRoots 开始,多个线程同时标记存活的对象,把当前对象引用的未标记对象加入队列,直到所有能访问到的对象都被标记;
-
终止标记:因为 Go 支持 Finalizer 特性,有些对象需要"复活",由单线程处理复活逻辑,同时复活相关对象及其引用的所有对象;
-
并发清理:直接清理没有被标记的对象,把标记为存活的对象作为下一次 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 的运行环境,尝试在更多核心业务场景中应用,为开发者提供一个无缝、高效的跨语言开发体验。