rust python扩展_从20秒到0.5秒:一个使用Rust语言来优化Python性能的案例

Sentry的Python source map处理器成为性能瓶颈,通过使用Rust语言重构,将大型source map的处理时间从20秒减少到0.5秒,显著降低了CPU利用率和内存开销,展示了Rust在性能密集型任务中的优势。
摘要由CSDN通过智能技术生成

71448a3fb002a9947ff1a6c3f670ae88.png

Sentry 是一个辅助在线营业举行监控及错误剖析的云服务,它每月处置跨越十亿次错误。我们已经能够扩展我们的大多数系统,但在已往几个月,Python 写的 source map 处置程序已经成为我们性能瓶颈所在。(译者:source map 就是将压缩或者混淆过的代码与原始代码的对应表)

从上周最先,基础设施团队决议观察 source map 处置程序的性能瓶颈。——我们的 Javascript 客户端已经成为我们最受迎接的程序,其中一个原因是我们通过 source map 反混淆 JavaScript 的能力。然而,处置操作不是没有价值的。我们必须获取,解压缩,反混淆然后反向扩张,使 JavaScript 客栈跟踪可读。

当我们在 4 年前编写了原始处置流水线时,source map 生态系统才刚刚最先演化。随着它发展为一个庞大而成熟的 source map 处置程序,我们花了许多时间用 Python 来处置问题。

停止昨天,我们通过 Rust 模块替换我们老的 Python 的 souce map 处置模块,大大削减了处置时间和我们的机械上的 CPU 利用率。

为了注释这一切,我们需要先明白 source map 和用 Python 的瑕玷。

Python 的 Source Maps

随着我们的用户的应用程序变得越来越庞大,他们的 source map 也越来越庞大。在 Python 中剖析 JSON 自己是足够快的,由于它们只是字符串而已。问题在于反序列化。每个 source map token 发生一个 Python 工具,我们有一些 source map 可能有几百万个 token。

将 source map token 反序列化的问题使得我们为基本 Python 工具支付伟大的成本。另外,所有这些工具都介入引用计数和垃圾网络,这进一步增加了开销。处置 30MB source map 使得单个 Python 历程在内存中扩展到〜 800MB,执行数百万次内存分配,并使垃圾网络器异常忙碌(译者注:token 是短生命周期工具,有新生代就好多了,这时候就体现出我大 Java 的优势了)。

由于这种反序列化需要工具头和垃圾接纳机制,我们能在 Python 层做改善的空间异常小。

Rust 的 Source Maps

在观察发现问题在于 Python 的性能缺陷后,我们决议实验 Rust source map 剖析器的性能,这是为我们的 CLI 工具编写的。在将 Rust 剖析器应用于问题很大的 source map 之后,其解释单独使用该库举行剖析可以将处置时间从 > 20 秒削减到 < 0.5 秒。这意味着纵然忽略任何优化,只是将 Python 剖析器替换为 Rust 剖析器就可以缓解我们的性能瓶颈。

我们证实 Rust 确实更快后,就清理了一些 Sentry 内部 API,以便我们可以用新的库替换原来的实现。这个 Python 库命名为 libsourcemap,是我们自己的 Rust source map 的一个薄包装。

优化效果

部署该库后,专门用于 source map 处置的机械压力大大降低。

ef46c02559512ab0cd3d98952944f549.png

最糟糕的 source map 处置时间削减到原来的十分之一。

981e8327cdf6daaed62c0d16f4bab4bf.png

更主要的是,平均处置时间削减到〜 400 ms。

fcbaf6ead1b15b6536a4fcea78c27584.png

JavaScript 是我们最受迎接的项目语言,这种转变达到了将所有事宜的端到端处置时间削减到〜 300 ms。

cbb99301500eda94224d7a8ffff7ba71.png

在 Python 中 嵌入 Rust

有许多方式可以露出 Rust 库给 Python。我们选择将 Rust 代码编译成一个 dylib,并提供一些 ol’C 函数,通过 CFFI 和 C 头文件露出给 Python。有了 C 语言头文件,CFFI 天生一些 shim( shim 是一个小型的函数库,用于透明地阻挡 API 挪用,修改通报的参数、自身处置操作、或把操作重定向到其他地方),可以挪用 Rust。这样,libsourcemap 可以打开在运行时从 Rust 天生的动态共享库。

这个历程有两个步骤。第一个是在 setup.py 运行时设置 CFFI 的构建模块:

643faa6a9966c3c5478d0a4744547e8b.png

在构建模块之后,头文件通过 C 预处置器来处置,以便扩展宏( CFFI 自己无法执行的历程)。此外,这将告诉 CFFI 在那里放置天生的 shim 模块。所有完成的之后,加载模块:

7b383b320e67b3d4f0642d24b30a2128.png

下一步是编写一些包装器代码来为 Rust 工具提供一个 Python API,这样能够转发异常。这发生在两个历程中:首先,确保在 Rust 代码中,我们尽可能使用效果工具。此外,我们需要处置好 panic,以确保他们不会跨越 DLL 界限。第二,我们界说了一个可以存储错误信息的辅助结构 ; 并将其作为 out 参数通报给可能失败的函数。

在 Python 中,我们提供了一个上下文管理器:

0203b00089d33679ab659092b9e04509.png

我们有一个特定错误类( special_errors)的字典,但若是没有找到详细的错误,将会抛一个通用的 SourceMapError。

从那里,我们现实上可以界说 source map 的基类:

8c22f7f3a4def282f4d285f0cde39c71.png

在 Rust 中露出 C ABI

我们从包罗一些导出函数的 C 头最先,若何从 Rust 导出它们? 有两个工具:特殊的# [no_mangle] 属性和 std :: panic 模块 ; 提供了 Rust panic 处置器。我们自己建立了一些 helper 来处置这个:一个函数用来通知 Python 发生了一个异常和两个异常处置 helper,一个通用的,另一个包装了返回值。有了这个,包装方式如下:

10e5cbdc2dab17ecc10edfb84c055841.png

boxed_landingpad 的事情方式很简单。它挪用闭包,用 panic :: catch_unwind 捕捉 panic,解开效果,并在原始指针中加上乐成值。若是发生错误,它会填充 err_out 并返回一个 指针。在 lsm_view_free 中,只需要从原始指针重新构建。

构建扩展

要现实构建扩展,我们必须在 setuptools 中做一些不太优雅的事情。幸运的是,在这件事上我们没有花太多时间,由于我们已经有一个类似的工具来处置。

这个做法最利便的部门是源代码用 cargo 编译,二进制安装最终的 dylib,消除任何最终用户使用 Rust 工具链的需要。

那些做得好,那些没做好?

我在 Twitter 上被问到:“ Rust 会有什么替换品?”说实话,Rust 很难替换。原因是,除非你想用性能更好的语言重写整个 Python 组件,否则只能使用本机扩展。在这种情况下,对语言的要求是相当苛刻的:它不能有一个侵入式运行时,不能有一个 GC,而且必须支持 C ABI。现在,我以为适合的语言是 C,C++ 和 Rust。

哪方面事情的好:

连系 Rust 和 Python 与 CFFI。有一些替换品,链接到 libpython,但构建更庞大。

在老一些的 CentOS 版本使用 Docker 来构建可移植的 Linux 容器。虽然这个历程是乏味的,然而差别的 Linux 发兴版和内核之间的稳定性的差异使得 Docker 和 CentOS 成为可接受的构建解决方案。

Rust 生态系统。我们使用 crates.io 的 serde 反序列化和 base64 库,两个库事情异常好。此外,mmap 支持使用由社区 memmap 提供的另一库。

哪方面事情的欠好:

迭代和编译时间真的可以更好。我们每次更改字符时都编译模块和头文件。

setuptools 步骤异常懦弱。我们可能花了更多的时间来使 setuptools 事情。幸运的是,我们以前做过一次,以是这次更容易。

虽然 Rust 对我们的事情辅助很大,毫无疑问,有许多需要改善。特别是,用于导出 C ABI(并使其对 Python 有用)的基础设施应该有很大改善空间。编译时间也不是很长(译者的话,不是很长的意思是可能够我沏杯茶,眷念 go 的编译速率)。希望增量编译将有所辅助。

下一步

实在我们另有更多的改善空间。我们可以以更高效的花样启动缓存,好比一组存储在内存中的结构体而不是使用剖析 JSON。特别是,若是与文件系统缓存配对,我们险些可以完全消除加载的成本,由于我们平分了索引,这可以使用 mmap 异常有用。

鉴于这个好的效果,我们很可能会评估 Rust 更多在未来处置一些 CPU 密集型的营业。然而,对于大多数其他操作,程序花更多的时间守候 IO。

小结

虽然这个项目取得了伟大的乐成,然则我们只花了很少的时间来实现。它降低了我们的处置时间,它也将辅助我们水平扩展。Rust 一直是这个事情的完善工具,由于它允许我们将昂贵的操作使用内陆库完成,而且不必使用 C 或 C ++(这不太适合这种庞大的义务)。虽然很容易在 Rust 中编写 source map 剖析器,然则使用 C / C++ 来完成的话,代码更多,且没那么有意思。

我们确实喜欢 Python,而且是许多 Python 开源设计的贡献者。虽然 Python 仍然是我们最喜欢的语言,但我们信赖在合适的地方使用合适的语言。Rust 被证实是这项事情的最佳工具,我们很喜悦看到 Rust 和 Python 将来会带给我们什么。

译者注:不熟悉 source map 的同砚请看阮一峰的这篇文章 http://www.ruanyifeng.com/blog/2013/01/ javascript_source_map.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值