性能提升 40 倍!我们用 Rust 重写了自己的项目
前言
Rust 已经悄然成为了最受欢迎的编程语言之一。作为一门新兴底层系统语言,Rust 拥有着内存安全性机制、接近于 C/C++ 语言的性能优势、出色的开发者社区和体验出色的文档、工具链和IDE 等诸多特点。本文将介绍笔者使用 Rust 重写项目并逐步落地生产环境的过程,以及在重写过程选择 Rust 的原因、遇到的问题以及使用 Rust 重写带来的成果。
我们目前正在使用 Rust 开发的项目叫做 KCL,目前全部实现代码已经在 Github 上开源。KCL 是一个基于约束的记录及函数领域编程语言,致力于通过成熟的编程语言技术和实践来改进特领域如云原生 kubernetes 领域的大量繁杂配置编写和安全策略校验等,致力于构建围绕配置的更好的模块化、扩展性和稳定性,更简单的逻辑编写,以及更快的自动化集成和良好的生态延展性。更具体的 KCL 使用场景请访问 KCL 网站,本文中不再过多赘述。
KCL 之前是使用 Python 编写的,出于用户使用体验、性能和稳定性的考虑,决定用 Rust 语言进行重写,并获得了以下好处:
- 更少的 Bug,源于 Rust 强大的编译检查和错误处理方式
- 语言端到端编译执行性能提升了 66%
- 语言前端解析器性能提升了 20 倍
- 语言中端语义分析器性能提升了 40 倍
- 语言编译器编译过程平均内存使用量变为原来 Python 版本的一半
我们遇到了什么问题
就像社区中同类型项目 deno, swc, turbopack, rustc 等编译器、构建系统或者运行时在技术上使用 Rust 做的事情类似,我们使用 Rust 完整构建了编译器的前中端和运行时,取得了一定的阶段性成果,但是我们大约在一年前并不是这个样子的。
一年前,我们使用 Python 语言构建了整个 KCL 语言编译器的实现,虽然在一开始的时候运行良好,Python 简单易上手,生态丰富,团队的研发效率也很高,但是随着代码库的扩张和工程师人数的增加,代码维护起来愈加困难,尽管我们在项目中强制编写 Python 类型注解,采用更严格的 lint 工具,代码测试行覆盖率也达到了 90% 以上,但是仍然会出现很多诸如 Python None 空对象,属性未找到等运行时才会出现错误,并且重构 Python 代码时也需要小心翼翼,反应到 KCL 语言上就是一个接一个的 bug, 严重影响用户使用体验。
此外,当 KCL 使用对象是广大开发者用户时,编程语言或者说编译器内部实现出现任何错误都是不可容忍的,这些也给我们的用户使用体验带来了一系列问题,使用 Python 编写的程序启动速度较慢,性能无法满足自动化系统在线编译和执行的效率诉求,因为在我们的场景中,用户修改 KCL 代码后需要能很快的展示编译结果,显然使用 Python 编写的编译器并不能很好地满足使用需求。
为什么选择 Rust
笔者所在团队基于如下原因选择了 Rust
- 使用 Go, Python, Rust 三种语言实现了简单的编程语言栈式虚拟机并作了性能对比,Go 和 Rust 在这个场景下性能接近,Python 有较大性能差距,综合考虑下采用了 Rust,具体三种语言实现的栈式虚拟机代码细节在 https://github.com/Peefy/StackMachine,感兴趣的同学可以前往浏览
- 越来越多的编程语言的编译器或运行时特别是前端基础设施项目采用 Rust 编写或重构,此外基础设施层,数据库、搜索引擎、网络设施、云原生、UI 层和嵌入式等领域都有 Rust 的出现,至少在编程语言领域实现方面经过了可行性和稳定性验证
- 考虑到后续的项目发展会涉及区块链和智能合约方向,而社区中大量的区块链和智能合约项目采用 Rust 编写
- 通过 Rust 获得更好的性能和稳定性,让系统更容易维护、更加健壮的同时,可以通过 FFI 暴露 C API 供多语言使用和扩展,方便生态扩展与集成
- Rust 对 WASM 的支持比较友好,社区中大量 WASM 生态是由 Rust 构建,KCL 语言和编译器可以借助 Rust 编译到 WASM 并在浏览器中运行
基于以上原因综合考虑选择了 Rust 而不是 Go,整个重写过程下来发现 Rust 综合素质确实过硬(第一梯队的性能,足够的抽象程度),虽然在一些语言特性特别是生命周期等上手成本有一些,生态上还不够丰富,总之编程语言可以做的事情,Rust 均可以做,具体可能还是要根据具体的场景和问题来做选择。同时如果想要使用好 Rust, 还需要深入理解内存、堆栈、引用、变量作用域等这些其它高级语言往往不会深入接触的内容。
使用 Rust 过程中遇到了哪些困难
虽然决定了使用 Rust 重写整个 KCL 项目,其实团队成员大部分成员是没有使用 Rust 编写一定代码体量项目的经验,包括笔者个人自己也仅仅学习过 《The Rust Programming Language》 中的部分内容,依稀记得学习到 Rc
和 RefCell
等智能指针内容就放弃了,那时没想到 Rust 中还能有与 C++ 中类似的东西。
使用 Rust 前预估的风险主要是 Rust 语言接触和学习的成本,这个确实在各种 Rust 的文章博客中均有提到,因为 KCL 项目整体架构并未发生太大变化,只是部分模块设计和代码编写针对 Rust 作了优化,因此整个重写是在边学边实践中进行。确实在刚开始使用 Rust 编写整个项目的时候花费在知识查询、编译排错的时间还是很多的,不过随着项目的进行渐入佳境,笔者个人经验使用 Rust 遇到的困难主要是心智转换和开发效率两方面:
心智转换
首先 Rust 的语法语义很好地吸收和融合了函数式编程中类型系统相关的概念,比如抽象代数类型 ADT 等,并且 Rust 中并无“继承”等相关概念,如果不能很好地理解甚至连其他语言中稀松平常的结构定义在 Rust 中可能都需要花费不少时间,比如如下的 Python 代码可能在 Rust 中的定义是这个样子的。
- Python
from dataclasses import dataclass
class KCLObject:
pass
@dataclass
class KCLIntObject(KCLObject):
value: int
@dataclass
class KCLFloatObject(KCLObject):
value: float
- Rust
enum KCLObject {
Int(u64),
Float(f64),
}
当然更多的时间是在与 Rust 编译器本身的报错作斗争,Rust 编译器会经常使开发人员"碰壁",比如借用检查报错等,特别是对于编译器来讲,它处理的核心结构是抽象语法树 AST,这是一个递归和嵌套的树结构,在 Rust 中有时很难兼顾变量可变性与借用检查的关系,就如 KCL 编译器作用域 Scope
的结构定义结构那样,对于存在循环引用的场景,用于需要显示意识到数据的相互依赖关系,而大量使用 Rc
, RefCell
和 Weak
等 Rust 中常用的智能指针结构。
/// A Scope maintains a set of objects and links to its containing
/// (parent) and contained (children) scopes. Objects may be inserted
/// and looked up by name. The zero value for Scope is a ready-to-use
/// empty scope.
#[derive(Clone, Debug)]
pub struct Scope {
/// The parent scope.
pub parent: Option<Weak<RefCell<Scope>>>,
/// The child scope list.
pub children: Vec<Rc<RefCell<Scope>>>,
/// The scope object mapping with its name.
pub elems: IndexMap<String, Rc<RefCell<ScopeObject>>>,
/// The scope start position.
pub start: Position,
/// The scope end position.
pub end: Position,
/// The scope kind.
pub kind: ScopeKind,
}
开发效率
Rust 的开发效率可以用先抑后扬来形容。在刚开始上手写项目时,如果团队成员没有接触过函数式编程相关概念以及相关的编程习惯,开发速度将显著慢于 Python、Go 和 Java 等语言,不过一旦开始熟悉 Rust 标准库常用的方法、最佳实践以及常见 Rust 编译器报错修改,开发效率将大幅提升,并且原生就能写出高质量、安全、高效的代码。
比如笔者个人当初遇到一个如下代码所示的与生命周期错误前前后后排查了很久的时间才发现原来是忘记标注生命参数导致生命周期不匹配。此外 Rust 的生命周期与类型系统、作用域、所有权、借用检查等概念耦合在一起,导致了较高的理解成本和复杂度,且报错信息往往不像类型错误那么明显,生命周期不匹配错误报错信息有时也略显呆板,可能会导致较高的排错成本,当然熟悉相关概念写多了之后效率会提高不少。
struct Data<'a> {
b: &'a u8,
}
// func1 和 func2 一个省略了生命周期参数,一个没有省略
// 对于 func2 的生命周期会由编译器缺省推导为 '_,可能导致生命周期不匹配错误
impl<'a> Data<'a> {
fn func1(&self) -> Data<'a> {Data { b: &0 }}
fn func2(&self) -> Data {Data { b: &0 }}
}
使用 Rust 重写收益比
经过团队几个人花费几个月时间使用 Rust 完全重写并稳定落地生产环境几个月后,回顾整个过程感觉这件事情的收获非常大,从技术角度层面来看,重写的过程不仅仅锻炼了快速学习一门新的编程语言、编程知识并将其付诸实践,并且整个重写过程让我们又反思了 KCL 编译器中设计不合理的部分并进行修改,对一个编程语言而言,这是一个长周期的项目,我们收获的是编译器系统更加稳定、安全,且代码清晰,bug 更少、性能更好的技术产品服务于用户,虽然没有全部模块得到高达 40 倍的性能,因为部分模块如 KCL 运行时的性能瓶颈在于内存深拷贝操作,但笔者个人认为仍然是值得的。且当 Rust 使用时间到达一定时长后,心智和开发效率不再是限制因素,就像学车那样,拿到驾照后更多是上路实践和总结。
结语
笔者个人觉得使用 Rust 重写项目后最重要的是不是我学会了一门新的编程语言,也不是 Rust 很流行很火因此我们在项目中采用一下,或者使用 Rust 编写了多少炫技的代码,是真真正正地使得语言和编译器本身更加稳定,能够在生产环境平稳落地并长期使用,启动速度和自动化效率不再受困扰,性能优于社区其他同类型领域编程语言,使我们语言和工具的用户感受到体验提升,这些都得益于 Rust 的无 GC、高性能、更好的错误处理内存管理、零抽象等特性。总之作为用户,他们才是最大的受益者。
最后,如果大家喜欢 KCL 语言这个项目,或想使用体验 KCL 用于自己的场景,或想使用 Rust 语言参与一个开源项目,欢迎大家访问 https://github.com/KusionStack/community 加入我们的社区一起参与讨论和共建 。
参考
- https://github.com/KusionStack/KCLVM
- https://github.com/Peefy/StackMachine
- https://doc.rust-lang.org/book/
- https://github.com/sunface/rust-course
- https://www.influxdata.com/blog/rust-can-be-difficult-to-learn-and-frustrating-but-its-also-the-most-exciting-thing-in-software-development-in-a-long-time/
性能提升 40 倍!我们用 Rust 重写了自己的项目的更多相关文章
- 性能提升40%: 腾讯 TKE 用 eBPF 绕过 conntrack 优化 K8s Service
Kubernetes Service 用于实现集群中业务之间的互相调用和负载均衡,目前社区的实现主要有userspace,iptables和IPVS三种模式.IPVS模式的性能最好,但依然有优化的空间 ...
- 腾讯 TKE 厉害了!用 eBPF绕过 conntrack 优化K8s Service,性能提升40%
Kubernetes Service[1] 用于实现集群中业务之间的互相调用和负载均衡,目前社区的实现主要有userspace,iptables和IPVS三种模式.IPVS模式的性能最好,但依然有优化 ...
- Web 应用性能提升 10 倍的 10 个建议
转载自http://blog.jobbole.com/94962/ 提升 Web 应用的性能变得越来越重要.线上经济活动的份额持续增长,当前发达世界中 5 % 的经济发生在互联网上(查看下面资源的统计 ...
- 重构、插件化、性能提升 20 倍,Apache DolphinScheduler 2.0 alpha 发布亮点太多!
点击上方 蓝字关注我们 社区的小伙伴们,好消息!经过 100 多位社区贡献者近 10 个月的共同努力,我们很高兴地宣布 Apache DolphinScheduler 2.0 alpha 发布.这是 ...
- 在MongoDB中创建一个索引而性能提升1000倍的小例子
在https://www.cnblogs.com/xuliuzai/p/9965229.html的博文中我们介绍了MongoDB的常见索引的创建语法.部分同学还想看看MongoDB的威力到底有多大,所 ...
- 有史以来性价比最高最让人感动的一次数据库&SQL优化(DB & SQL TUNING)——半小时性能提升千倍
昨天,一个客户现场人员急急忙忙打电话找我,说需要帮忙调优系统,因为经常给他们干活,所以,也就没多说什么,先了解情况,据他们说,就是他们的系统最近才出现了明显的反应迟钝问题,他们的那个系统我很了解,软硬 ...
- 如何利用缓存机制实现JAVA类反射性能提升30倍
一次性能提高30倍的JAVA类反射性能优化实践 文章来源:宜信技术学院 & 宜信支付结算团队技术分享第4期-支付结算部支付研发团队高级工程师陶红<JAVA类反射技术&优化> ...
- 存算分离下写性能提升10倍以上,EMR Spark引擎是如何做到的?
引言 随着大数据技术架构的演进,存储与计算分离的架构能更好的满足用户对降低数据存储成本,按需调度计算资源的诉求,正在成为越来越多人的选择.相较 HDFS,数据存储在对象存储上可以节约存储成本,但与此 ...
- Nacos 2.0 正式发布,性能提升 10 倍!!
3月20号,Nacos 2.0.0 正式发布了! Nacos 简介: 一个更易于构建云原生应用的动态服务发现.配置管理和服务管理平台. 通俗点讲,Nacos 就是一把微服务双刃剑:注册中心 + 配置中 ...
- 查询性能提升3倍!Apache Hudi 查询优化了解下?
从 Hudi 0.10.0版本开始,我们很高兴推出在数据库领域中称为 Z-Order 和 Hilbert 空间填充曲线的高级数据布局优化技术的支持. 1. 背景 Amazon EMR 团队最近发表了一 ...
随机推荐
- vue引入高德地图
一,下载 npm install vue-amap --save 二,main.js文件内引入 import VueAMap from 'vue-amap' Vue.use(VueAMap); // ...
- WCH网络授时芯片CH9126操作指导
目前CH9126推荐在Win7操作系统的电脑上执行,暂不推荐在与Win10系统的电脑进行通讯. 一.重要引脚说明 设置及状态相关引脚: 引脚1:RSETE-一个控制CH9126工号的引脚,直接接18K ...
- 线性方程组的直接解法——Gauss消去法
考虑线性方程组 \[\mathrm{A}x=\mathrm{b} \] 其中,\(\mathrm{A}=(a_{ij})_{n\times n}\),\(\mathrm{b}=[b_1,b_2,\cd ...
- [C++标准模板库:自修教程与参考手册]关于vector
什么是vector 可以这样认为,vector就是一个动态的数组,其中的元素必须具备assignable(可赋值)和copyable(可拷贝)两个性质. vector的一些重要的性质 vector支持 ...
- 使用Logstash工具导入sqlserver数据到elasticSearch及elk分布式日志中心
首先记下这个笔记,Logstash工具导入sqlserver数据到elasticSearch. 因为logstash使用java写的,我本地开发是win11,所以javade jdk必须要安装.具体安 ...
- 解析url地址hashhref
- 玩转web3第二篇——Web3UI Kit
介绍 开发web2应用的时候,可以很方便找到很多优秀的UI库,比如antd,material ui,element ui等等,但web3应用对应的UI库却不多. 今天给大家介绍一款优秀的WEB3的UI ...
- drf入门规范、序列化器组件、视图组件、请求与响应
DRF框架之入门规范 本篇文章会详细介绍web开发模式.API接口及其接口测试工具.restful规范.还有经常分不清又很重要的序列化与反序列化的部分,初级交接触APIView.Request类. ...
- Spring中常见的注解
1.组件注解 @Controller @Service @Repository @Component ---标注一个类为Spring容器的Bean @Configration ---声明当前类为配置类 ...
- 在windows系统下用vscode构造shell脚本IDE
1.基础环境搭建 安装Visual Studio Code(VScode ) 下载地址:https://code.visualstudio.com/Download 下载完双击文件,选择路径安装即可, ...