性能提升 40 倍!我们用 Rust 重写了自己的项目 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
peefy
V2EX    程序员

性能提升 40 倍!我们用 Rust 重写了自己的项目

  •  1
     
  •   peefy 2023-01-30 20:20:34 +08:00 7082 次点击
    这是一个创建于 995 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前言

    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 》 中的部分内容,依稀记得学习到 RcRefCell 等智能指针内容就放弃了,那时没想到 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, RefCellWeak 等 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 加入我们的社区一起参与讨论和共建 。

    参考

    31 条回复    2023-02-01 13:50:0 +08:00
    littlewing
        1
    littlewing  
       2023-01-30 20:26:12 +08:00   18
    提升 40 倍只能说明之前的代码写得烂
    e3c78a97e0f8
        2
    e3c78a97e0f8  
       2023-01-30 20:34:28 +08:00 via iPhone   1
    比 Python 快 40 倍真心没什么,用 Java 写用 Go 写都能快几十倍
    centralpark
        3
    centralpark  
       2023-01-30 20:44:00 +08:00   1
    第一个把女人比做花的是天才,第二个把女人比做花的是庸才,第三个把女人比做花的是蠢才。

    用 rust 提升几十倍性能的文章真的看得有点审美疲劳了……
    Hanggi
        4
    Hanggi  
       2023-01-30 21:01:02 +08:00   1
    除了性能,选择语言要看几个方面,

    一是代码编写易用性,同一段逻辑编写不同语言的复杂度和可读性
    二是看编码效率,C++ 性能也很快,但是编译速度慢 (虽然没有是测过,但是听说 Rust 项目大了编译也慢)
    三是看语言的生态,是否有足够多而成熟的解决方案
    最后还要看语言占用资源的效率,如果经费吃紧也要纳入考量

    想问一下楼主,平时本地编码,改几行代码重启服务要多久?(只是想了解一下)
    victorc
        5
    victorc  
       2023-01-30 21:12:22 +08:00   5
    为了简化配置编写,发明一个 DSL ,然后为了这个 DSL 生成更快,又用 rust 改写生成器,这属于精力过剩啊

    其实可以干点别的行业,说不定更幸福
    opentrade
        6
    opentrade  
       2023-01-30 21:17:09 +08:00
    宣传技巧过时
    matrix1010
        7
    matrix1010  
       2023-01-30 21:20:06 08:00
    "尽管我们在项目中强制编写 Python 类型注解,采用更严格的 lint 工具,代码测试行覆盖率也达到了 90% 以上,但是仍然会出现很多诸如 Python None 空对象,属性未找到等运行时才会出现错误,并且重构 Python 代码时也需要小心翼翼,反应到 KCL 语言上就是一个接一个的 bug, 严重影响用户使用体验" 这段令我对原先的 Python 版本比较好奇,想看看代码。但似乎现在这个 KCLVM 的 repo 完全是为 Rust 建的?
    ruanyu1
        8
    ruanyu1  
       2023-01-30 21:22:32 +08:00   3
    评论区戾气好重啊...人家就写个文章分享下经验而已,大过节的,没必要这样啊...
    smallboy19991231
        9
    smallboy19991231  
       2023-01-30 21:29:16 +08:00 via Android
    结语说的挺不错的
    acerphoenix
        10
    acerphoenix  
       2023-01-30 21:59:45 +08:00
    @e3c78a97e0f8 在 java 统治领域不可能
    makelove
        11
    makelove  
       2023-01-30 22:05:44 +08:00
    评论区这莫名奇妙的
    连脚本性能一哥的我大 Javascript 生态搞 rust 转换都有数量级提升,更别说性能垫底的 python
    seakingii
        12
    seakingii  
       2023-01-30 22:12:54 +08:00   2
    @ruanyu1 没有任何价值的"分享文章",全是泛泛而谈,还都是老调重谈
    而且用 RUST 和 PYTHON 对比也太离谱了,天差地别
    没有任何具体的测试数据
    纯纯的标题党
    纯纯的软广

    多天真,才认为是"分享文章"?
    jjx
        13
    jjx  
       2023-01-30 22:16:31 +08:00   1
    rust 对标的是 c/c++

    你同它比

    同 python 比什么啊

    话说我曾经用 python 重构过一个 c#项目,性能大大改善, 说明了什么? 无非是重构时用心设计了
    David1119
        14
    David1119  
       2023-01-30 22:18:15 +08:00   1
    “速度提升 40 倍!我们把团队交通工具统一从自行车换成了轿车!”
    aru
        15
    aru  
       2023-01-30 22:31:48 +08:00
    @David1119 自行车速度 20km/h, 汽车 120 km/h ,并不能提升 40 倍
    agagega
        16
    agagega  
       2023-01-30 23:52:26 +08:00
    C/C++程序员看到这种文章也就图一乐,别看多了让某些前端(不针对所有)真被忽悠瘸了
    crayygy
        17
    crayygy  
       2023-01-31 11:14:33 +08:00
    @aru #13 原本也有可能是在推着自行车走
    TuneG
        18
    TuneG  
       2023-01-31 11:28:15 +08:00
    一直在搞 C++,最近正好想研究一下 rust ,对比一下,提升理解,感谢
    adian
        19
    adian  
       2023-01-31 12:10:48 +08:00
    感谢分享,酷
    alexsunxl
        20
    alexsunxl  
       2023-01-31 12:17:23 +08:00
    @TuneG c++的搞 rust 容易回不去。哈哈哈
    wuwukai007
        21
    wuwukai007  
       2023-01-31 18:50:36 +08:00
    你敢说重写的时候没有一些局部优化吗?
        22
    peefy  
    OP
       2023-02-01 12:50:34 +08:00
    @matrix1010 原先版本的代码我们也是开源的,kclvm-py 这个 repo
    peefy
    peefy
        23
    peefy  
    OP
       2023-02-01 12:52:05 +08:00
    @victorc 主要是分享一下过去的工作,现在看应该是技术选型问题和资源问题
    peefy
        24
    peefy  
    OP
       2023-02-01 12:53:40 +08:00
    @wuwukai007 文章中有提到一点点优化的,不过占比不是很大,90% 以上语法解析,语义分析的逻辑就是 1 比 1 重写,具体可以对比 Github 上 kclvm-py 和 KCLVM 两个仓库
    peefy
        25
    peefy  
    OP
       2023-02-01 12:54:47 +08:00
    @alexsunxl 当然反过来说 rust 比 C++ 的生态也差了不少,我们在重写过程中发现社区很多 Rust 库都问题不少,但是 C++ 的库就很多了
    peefy
        26
    peefy  
    OP
       2023-02-01 12:56:06 +08:00
    @centralpark 谢谢,主要是分享一下自己过去工作的经验和教训,现在看主要是技术选型和资源投入上走了弯路。我个人其实用 Python 远大于 Rust
    peefy
        27
    peefy  
    OP
       2023-02-01 12:57:57 +08:00
    @littlewing 实际上之后的 Rust 代码主要是对原有 Python 代码的改进,后面的 Rust 代码也会有一部分提升空间,因为是一边学习 Rust 一边重写,相比于 Rust, 题主的 Python 经验更丰富一些,当然原有代码不一定写的好,但是也花了很大精力去优化性能
    peefy
        28
    peefy  
    OP
       2023-02-01 13:04:38 +08:00
    @Hanggi Rust 编译速度还是比较慢的,技术选型方面完全同意您的看法
    matrix1010
        29
    matrix1010  
       2023-02-01 13:09:26 +08:00
    @peefy 确实有 type hints ,但我大概看了一眼 vm.py 的 Frame 有很多 name: str = None 这样的代码。这个 lint 通过不了吧。也解释了你自己说的 None 问题
    peefy
        30
    peefy  
    OP
       2023-02-01 13:10:32 +08:00
    @Hanggi Rust 编译速度还是比较慢的,技术选型上完全同意您的看法
    peefy
        31
    peefy  
    OP
       2023-02-01 13:50:04 +08:00
    @matrix1010 嗯嗯,是的。这块代码没有处理好,加上更早期的时候没有给 py 代码加 type hints ,对 python 的不熟悉导致后续加 type hints 的时候向 mypy 这种工具很难没错误的利用起来,工程性做的不是很好。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5897 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 36ms UTC 06:15 PVG 14:15 LAX 23:15 JFK 02:15
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86