热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

注:转自微信公众号“高可用架构”:从20秒到0.5秒:一个使用Rust语言来优化Python性能的案例导读:Python被很

注: 转自 微信公众号“高可用架构”:从20秒到0.5秒:一个使用Rust语言来优化Python性能的案例


导读:Python 被很多互联网系统广泛使用,但在另外一方面,它也存在一些性能问题,不过 Sentry 工程师分享的在关键模块上用另外一门语言 Rust 来代替 Python 的情况还是比较罕见,也在 Python 圈引发了热议,高可用架构小编将文章翻译转载如下。
clipboard.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 的性能缺陷后&#xff0c;我们决定尝试 Rust source map 解析器的性能&#xff0c;这是为我们的 CLI 工具编写的。在将 Rust 解析器应用于问题很大的 source map 之后&#xff0c;其表明单独使用该库进行解析可以将处理时间从 > 20 秒减少到 <0.5 秒。这意味着即使忽略任何优化&#xff0c;只是将 Python 解析器替换为 Rust 解析器就可以缓解我们的性能瓶颈。

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

优化结果

部署该库后&#xff0c;专门用于 source map 处理的机器压力大大降低。

clipboard.png

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

clipboard.png

更重要的是&#xff0c;平均处理时间减少到〜 400 ms。

clipboard.png

Javascript 是我们最受欢迎的项目语言&#xff0c;这种变化达到了将所有事件的端到端处理时间减少到〜 300 ms。

clipboard.png

在 Python 中 嵌入 Rust

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

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

clipboard.png

在构建模块之后&#xff0c;头文件通过 C 预处理器来处理&#xff0c;以便扩展宏&#xff08; CFFI 本身无法执行的过程&#xff09;。此外&#xff0c;这将告诉 CFFI 在哪里放置生成的 shim 模块。所有完成的之后&#xff0c;加载模块&#xff1a;

clipboard.png

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

在 Python 中&#xff0c;我们提供了一个上下文管理器&#xff1a;

clipboard.png

我们有一个特定错误类&#xff08; special_errors&#xff09;的字典&#xff0c;但如果没有找到具体的错误&#xff0c;将会抛一个通用的 SourceMapError。

从那里&#xff0c;我们实际上可以定义 source map 的基类&#xff1a;

clipboard.png

在 Rust 中暴露 C API

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

clipboard.png

boxed_landingpad 的工作方式很简单。它调用闭包&#xff0c;用 panic :: catch_unwind 捕获 panic&#xff0c;解开结果&#xff0c;并在原始指针中加上成功值。如果发生错误&#xff0c;它会填充 err_out 并返回一个 NULL 指针。在 lsm_view_free 中&#xff0c;只需要从原始指针重新构建。

构建扩展

要实际构建扩展&#xff0c;我们必须在 setuptools 中做一些不太优雅的事情。幸运的是&#xff0c;在这件事上我们没有花太多时间&#xff0c;因为我们已经有一个类似的工具来处理。

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

那些做得好&#xff0c;那些没做好&#xff1f;

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

哪方面工作的好&#xff1a;

  • 结合 Rust 和 Python 与 CFFI。有一些替代品&#xff0c;链接到 libpython&#xff0c;但构建更复杂。

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

  • Rust 生态系统。我们使用 crates.io 的 serde 反序列化和 base64 库&#xff0c;两个库工作非常好。此外&#xff0c;mmap 支持使用由社区 memmap 提供的另一库。

哪方面工作的不好&#xff1a;

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

  • setuptools 步骤非常脆弱。我们可能花了更多的时间来使 setuptools 工作。幸运的是&#xff0c;我们以前做过一次&#xff0c;所以这次更容易。

虽然 Rust 对我们的工作帮助很大&#xff0c;毫无疑问&#xff0c;有很多需要改进。特别是&#xff0c;用于导出 C ABI&#xff08;并使其对 Python 有用&#xff09;的基础设施应该有很大改进空间。编译时间也不是很长&#xff08;译者的话&#xff0c;不是很长的意思是可能够我沏杯茶&#xff0c;怀念 go 的编译速度&#xff09;。希望增量编译将有所帮助。

下一步

其实我们还有更多的改进空间。我们可以以更高效的格式启动缓存&#xff0c;比如一组存储在内存中的结构体而不是使用解析 JSON。特别是&#xff0c;如果与文件系统缓存配对&#xff0c;我们几乎可以完全消除加载的成本&#xff0c;因为我们平分了索引&#xff0c;这可以使用 mmap 非常有效。

鉴于这个好的结果&#xff0c;我们很可能会评估 Rust 更多在未来处理一些 CPU 密集型的业务。然而&#xff0c;对于大多数其他操作&#xff0c;程序花更多的时间等待 IO。

小结

虽然这个项目取得了巨大的成功&#xff0c;但是我们只花了很少的时间来实现。它降低了我们的处理时间&#xff0c;它也将帮助我们水平扩展。Rust 一直是这个工作的完美工具&#xff0c;因为它允许我们将昂贵的操作使用本地库完成&#xff0c;而且不必使用 C 或 C &#43;&#43;&#xff08;这不太适合这种复杂的任务&#xff09;。虽然很容易在 Rust 中编写 source map 解析器&#xff0c;但是使用 C / C&#43;&#43; 来完成的话&#xff0c;代码更多&#xff0c;且没那么有意思。

我们确实喜欢 Python&#xff0c;并且是许多 Python 开源计划的贡献者。虽然 Python 仍然是我们最喜欢的语言&#xff0c;但我们相信在合适的地方使用合适的语言。Rust 被证明是这项工作的最佳工具&#xff0c;我们很高兴看到 Rust 和 Python 将来会带给我们什么。

译者注&#xff1a;不熟悉 source map 的同学请看阮一峰的这篇文章 http://www.ruanyifeng.com/blo...



推荐阅读
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • ### 优化后的摘要本学习指南旨在帮助读者全面掌握 Bootstrap 前端框架的核心知识点与实战技巧。内容涵盖基础入门、核心功能和高级应用。第一章通过一个简单的“Hello World”示例,介绍 Bootstrap 的基本用法和快速上手方法。第二章深入探讨 Bootstrap 与 JSP 集成的细节,揭示两者结合的优势和应用场景。第三章则进一步讲解 Bootstrap 的高级特性,如响应式设计和组件定制,为开发者提供全方位的技术支持。 ... [详细]
  • 本文介绍了 Go 语言中的高性能、可扩展、轻量级 Web 框架 Echo。Echo 框架简单易用,仅需几行代码即可启动一个高性能 HTTP 服务。 ... [详细]
  • 本文将介绍如何在混合开发(Hybrid)应用中实现Native与HTML5的交互,包括基本概念、学习目标以及具体的实现步骤。 ... [详细]
  • RocketMQ在秒杀时的应用
    目录一、RocketMQ是什么二、broker和nameserver2.1Broker2.2NameServer三、MQ在秒杀场景下的应用3.1利用MQ进行异步操作3. ... [详细]
  • 2020年9月15日,Oracle正式发布了最新的JDK 15版本。本次更新带来了许多新特性,包括隐藏类、EdDSA签名算法、模式匹配、记录类、封闭类和文本块等。 ... [详细]
  • javax.mail.search.BodyTerm.matchPart()方法的使用及代码示例 ... [详细]
  • 本文最初发表在Thorben Janssen的Java EE博客上,每周都会分享最新的Java新闻和动态。 ... [详细]
  • 多线程基础概览
    本文探讨了多线程的起源及其在现代编程中的重要性。线程的引入是为了增强进程的稳定性,确保一个进程的崩溃不会影响其他进程。而进程的存在则是为了保障操作系统的稳定运行,防止单一应用程序的错误导致整个系统的崩溃。线程作为进程的逻辑单元,多个线程共享同一CPU,需要合理调度以避免资源竞争。 ... [详细]
  • Framework7:构建跨平台移动应用的高效框架
    Framework7 是一个开源免费的框架,适用于开发混合移动应用(原生与HTML混合)或iOS&Android风格的Web应用。此外,它还可以作为原型开发工具,帮助开发者快速创建应用原型。 ... [详细]
  • 本文总结了一些开发中常见的问题及其解决方案,包括特性过滤器的使用、NuGet程序集版本冲突、线程存储、溢出检查、ThreadPool的最大线程数设置、Redis使用中的问题以及Task.Result和Task.GetAwaiter().GetResult()的区别。 ... [详细]
  • 在JavaWeb开发中,文件上传是一个常见的需求。无论是通过表单还是其他方式上传文件,都必须使用POST请求。前端部分通常采用HTML表单来实现文件选择和提交功能。后端则利用Apache Commons FileUpload库来处理上传的文件,该库提供了强大的文件解析和存储能力,能够高效地处理各种文件类型。此外,为了提高系统的安全性和稳定性,还需要对上传文件的大小、格式等进行严格的校验和限制。 ... [详细]
  • 您的数据库配置是否安全?DBSAT工具助您一臂之力!
    本文探讨了Oracle提供的免费工具DBSAT,该工具能够有效协助用户检测和优化数据库配置的安全性。通过全面的分析和报告,DBSAT帮助用户识别潜在的安全漏洞,并提供针对性的改进建议,确保数据库系统的稳定性和安全性。 ... [详细]
  • Hyperledger Fabric 1.4 节点 SDK 快速入门指南
    本文将详细介绍如何利用 Hyperledger Fabric 1.4 的 Node.js SDK 开发应用程序。通过最新版本的 Fabric Node.js SDK,开发者可以更高效地构建和部署基于区块链的应用,实现数据的安全共享和交易处理。文章将涵盖环境配置、SDK 安装、示例代码以及常见问题的解决方法,帮助读者快速上手并掌握核心功能。 ... [详细]
  • 在PHP中实现腾讯云接口签名,以完成人脸核身功能的对接与签名配置时,需要注意将文档中的POST请求改为GET请求。具体步骤包括:使用你的`secretKey`生成签名字符串`$srcStr`,格式为`GET faceid.tencentcloudapi.com?`,确保参数正确拼接,避免因请求方法错误导致的签名问题。此外,还需关注API的其他参数要求,确保请求的完整性和安全性。 ... [详细]
author-avatar
exit佑
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有