加入收藏 | 设为首页 | 会员中心 | 我要投稿 应用网_丽江站长网 (http://www.0888zz.com/)- 科技、建站、数据工具、云上网络、机器学习!
当前位置: 首页 > 服务器 > 安全 > 正文

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

发布时间:2021-01-08 13:55:44 所属栏目:安全 来源:网络整理
导读:副标题#e# 《从20秒到0.5秒:一个使用Rust语言来优化Python性能的案例》要点: 本文介绍了从20秒到0.5秒:一个使用Rust语言来优化Python性能的案例,希望对您有用。如果有疑问,可以联系我们。 导读:Python 被很多互联网系统广泛使用,但在另外一方面,它也
副标题[/!--empirenews.page--]

《从20秒到0.5秒:一个使用Rust语言来优化Python性能的案例》要点:
本文介绍了从20秒到0.5秒:一个使用Rust语言来优化Python性能的案例,希望对您有用。如果有疑问,可以联系我们。

导读:Python 被很多互联网系统广泛使用,但在另外一方面,它也存在一些性能问题,不过 Sentry 工程师分享的在关键模块上用另外一门语言 Rust 来代替 Python 的情况还是比较罕见,也在 Python 圈引发了热议,高可用架构小编将文章翻译转载如下.

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 处理的机器压力大大降低.

最糟糕的 source map 处理时间减少到原来的十分之一.

更重要的是,平均处理时间减少到? 400 ms.

JavaScript 是我们最受欢迎的项目语言,这种变化达到了将所有事件的端到端处理时间减少到? 300 ms.

在 Python 中 嵌入 Rust

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

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

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

下一步是编写一些包装器代码来为 Rust 对象提供一个 Python API,这样能够转发异常.这发生在两个过程中:首先,确保在 Rust 代码中,我们尽可能使用结果对象.此外,我们需要处理好 panic,以确保他们不会跨越 DLL 边界.第二,我们定义了一个可以存储错误信息的帮助结构 ; 并将其作为 out 参数传递给可能失败的函数.

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

我们有一个特定错误类( special_errors)的字典,但如果没有找到具体的错误,将会抛一个通用的 SourceMapError.

从那里,我们实际上可以定义 source map 的基类:

在 Rust 中暴露 C ABI

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

(编辑:应用网_丽江站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读