budshome (blog: 芽之家) shared the aphorism --
问题的复杂性不在于问题本身,而是找到问题后,你如何去处理。 -- 佚名

[Rust] 使用 Rust 极致提升 Python 性能:图表和绘图提升 24 倍,数据计算提升 10 倍

💥 内容涉及著作权,均归属作者本人。若非作者注明,默认欢迎转载:请注明出处,及相关链接。

Summary: 有时候,仅采用标准方法还不够好。本篇文章,是关于在重要的地方做最小的改变,从而达到最大的效果。处理来自船舶的 GPS 信号时,使用 Python 各种类库优化后,性能仍然不能满足。通过大约 300 行 Rust 代码,包含细节的整个实现,甚至包括 Rust 文档和单元测试!使得数据处理的速度大幅提升:图表和绘图提升 24 倍,数据计算提升 10 倍。生产环境中的体现,表明这是经过深思熟虑的、有针对性的优化。这次改进,不仅仅是学术上的,也不仅仅是为了降低工作成本。

Topics: rust python pyo3 rust-python-集成 python-性能改进

关于作者

Edward Wright

Vortexa 公司的首席 GIS 工程师。不写代码的时候,他忙着跑步机、山地自行车、建筑、修理东西,以及油画。

有时候,仅采用标准方法,还不够好。本篇文章,是关于在重要的地方做最小的改变,从而达到最大的效果。

问题的边界

在 vortex 公司,我们广泛使用 Python。Python 非常适合于原型设计,也非常适合于数据的科学计算。虽然 Python 不是最快的语言,但它通常是非常棒的。

然而,最近我们发现一个特定的 Python 任务,需要 30 小时才能运行完毕。由于一些模型的变更,当我们想对一些业务调用重新计算时,这个运行时间真的影响了我们的 QA 反馈周期,使得将更新的模型引入到生产环境,变得更加困难。如果我们能够解决这个问题,将会加速模型的改进,为团队和我们的客户带来真正的好处。

在没有太多无关细节的情况下,我们的任务是处理来自船舶的 GPS 信号,并在应用其它算法之前,通过一组多边形算法,对信号进行过滤。

为什么这段代码如此慢?

无需做假设,我们的出发点必须是先测量这段代码。

我创建了代码的一个副本(复制/粘贴即可),但对其进行了修改,以便于可以处理一个小数据集。并在将来,对不同的技术进行比较。这段测试的代码,仍然忠实地再现了生产环境中所部署代码的运行负载。我使用优秀的 pyinstrument 模块,深入了解了 Python 代码中正在发生的事情。为了防止由于运行时间过短而扭曲结果,在所有初始化工作完成后,我才开始分析。

结果如下:

测量 Python 代码

时间单位为秒。

main 方法,代表了算法在完成整个初始化之后的处理过程。test_python 方法,正在测试我认为很慢的部分代码的逻辑。当然,所有其它代码的逻辑仍然是存在的。

所以在 34.3 秒的运行时间中,29.8 秒花在了我前面提到的过滤逻辑中,25.1 秒消耗在 matplotlib 处理中,主要是做多边形绘图运算。

哪儿有问题?

我进行的测试数据,使用了近 8 米的船舶定位。我们正在研究全世界的数百个区域,数百个实现过滤功能的多边形算法要运行。我们使用的是 pandas,船舶的位置存储在 dataframe,但是我们需要将这个 dataframe 传递给 matplotlib,用于我们要测试的每个多边形区域。

我们对一个库进行了数百次调用,每次都要传递数百万条记录。在生产环境中,我们处理的数据可能要增加到 2500 倍,因此使用者才能看到 30 小时内,船舶的位置数据来自何处。

如何处理?

或许,在生产环境中进行繁重的任务处理,matplotlib 不是合适的工具?既然代码中已经在使用 pandas 了,为什么不试试 geopandas 呢?然后,我们可以在一个库调用中,计算所有多边形区域。

然而,这是一个灾难,我们增加了 10 倍的运行时间!Geopandas(以及它依次调用的其它库)使用了 423 个堆栈帧,而 matplotlib 只使用了 5 个堆栈帧,我觉得这非常惊人。测试跟踪还显示,即使创建 GeoDataFrames,也要比基于 matplotlib 的整体处理,花费更长的时间。

所以,我们有一个选择题。我们可以:

  1. 尝试将数据分块,然后使用多进程 multi-processing 模块处理(在 Python 中是不推荐的),从而利用更强大的云虚拟机,用来支撑 matplotlib 计算。
  2. 使用线程,编写一个非常小的本地自定义库,用来完成我们想要的数学运算。

第一种方法可以工作,但不太可能是非常经济高效的,因为我们只是并行地运行多个较慢代码的副本。于是,我决定试试第二种选择。

规划自定义本地库

考虑到在早期的 Java point-in-polygon 开发中,吸取到的一些经验教训,这次我们可以使用一些技巧。例如:

  1. 避免为每个多边形计算都进行库调用,为每个 dataframe 只进行一次调用,可以大量减少库调用的开销。
  2. 避免在实际问题非常简单的情况下,使用重量级几何计算库,否则开销会严重影响性能。
  3. 对每个多边形进行边界测试。
  4. 尽可能基于 32 位整数(比浮点更快)。
  5. 使用线程。

需要说明的是,Java 肯定不是这里的答案。Java 与 Python 的集成,真是太吓人了。

Rust

最近,我一直在使用 PyO3 做一些实验性的工作,它允许 Rust / Python 的双向集成。这里,我们将重点介绍 Python 导入和使用 Rust 实现的模块。

PyO3

以下是实现的功能明细:

  1. 在 Rust 中实现 Python 类。
  2. 在构造函数中,存放 geojson 字符串数组,表示我们的多边形区域。
  3. 从船舶位置 dataframe,获取纬度/经度坐标,存入 numpy 数组。
  4. 返回结果为 numpy 数组(便于与 Python pandas 集成),表示每个坐标集对应的多边形(如果有的话)。

包含细节的整个实现,需要大约 300 行 Rust 代码,甚至包括 Rust 文档和单元测试!并且,还替换了大约 30 行 Python 代码(增加对 matplotlib 的调用)。PyO3 可以很好地与 numpyndarray crate(Rust 库)配合使用,允许其轻松地与 pandas 以及 numpy array 集成。并行处理方面,我们使用了 rayon

有用吗?

当然有用。否则,这篇博文会很无聊的……

Python 调用 Rust

测试数据是完全相同的。

“使用 Rust,我们已经将 matplotlib 的处理时间,从 29.8 秒减少到 2.9 秒。”

Python 只使用一个线程,而 Rust 使用了 8 个线程(intel i7,超线程 4 核,所以称之为 4-5 倍的有效计算)。这还包括 Python 将结果集插回 pandas dataframe 的时间消耗。将实际的 matplotlib 与 Rust 库调用进行比较,可以得到 24 倍的改进。输出数据已经检查过,结果显示完全相同。

我们的新解决方案(在功能级别,即 dataframe 输入/输出),速度提高了 10 倍。当我们的目标是更快的 QA 周期时,需要最终部署在 AWS 中的 Kubernetes 集群中。集群中运行的代码,将其计算核心数量增加到 4 个,是完全合理的。考虑到后续的过滤算法,Rust 处理时间约占任务总运行时间的 20%,因此添加更多线程几乎没有意义,除非任务的其他部分可以受益。

生产环境的提升

以上小修改的具体代码,已经部署在正式生产环境中。上文提到,数据量会扩大到 2500 倍。

“这个处理过程,过去需要 30 个小时,现在需要 6 个小时,速度提升 500%。”

这次改进,不仅仅是学术上的,也不仅仅是为了降低工作成本。

“我们为客户带来模型变更后的内部流程,包括 QA,现在比以前快了一天——每次都快。”

这是经过深思熟虑的、有针对性的优化。

我们必须考虑到,我们在这里添加了一项新技术,使代码复杂化了,并使维护源代码存储库变得更加困难。但是,通过限制新库的功能实现范围,具体地小改进,可以缓解这种情况。业务逻辑没有改变,但实现方式已经改变了,只要 point-in-polygon “正常工作”——我们有单元测试来证明这一点——这次代码改进就不会造成任何伤害。

原文链接:Using Rust to corrode insane Python run-times

谢谢您的阅读!


Rust 生态与实践

Related Articles

  1. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 WebAssembly 博客应用的体验报告
  2. [Rust] Rust 官方周报 399 期(2021-07-14)
  3. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 web 前端(5)- 构建 HTTP 请求、与外部服务器通信的两种方法
  4. [Rust] Rust 官方周报 398 期(2021-07-07)
  5. [Rust] Rust 官方周报 397 期(2021-06-30)
  6. [Rust] Rust 官方周报 396 期(2021-06-23)
  7. [Rust] Rust 官方周报 395 期(2021-06-16)
  8. [Rust] Rust 1.53.0 明日发布,关键新特性一瞥
  9. [Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(3)- rhai 脚本、静态/资源文件、环境变量等
  10. [Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(2)- 获取并解析 GraphQL 数据
  11. [Rust] 使用 tide、handlebars、rhai、graphql 开发 Rust web 前端(1)- crate 选择及环境搭建
  12. [Rust] Rust 官方周报 394 期(2021-06-09)
  13. [Rust] Rust web 前端库/框架评测,以及和 js 前端库/框架的比较
  14. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 web 前端(4)- 获取 GraphQL 数据并解析
  15. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 web 前端(3)- 资源文件及小重构
  16. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 WebAssembly 标准的 web 前端(2)- 组件和路由
  17. [WebAssembly] Rust 和 Wasm 的融合,使用 yew 构建 WebAssembly 标准的 web 前端(1)- 起步及 crate 选择
  18. [Rust] Rust 官方周报 393 期(2021-06-02)
  19. [Rust] Rust 官方周报 392 期(2021-05-26)
  20. [Rust] Rust 中,对网址进行异步快照,并添加水印效果的实践
  21. [Rust] Rust 官方周报 391 期(2021-05-19)
  22. [Rust] Rust,风雨六载,砥砺奋进
  23. [Rust] 为什么我们应当将 Rust 用于嵌入式开发?
  24. [Rust] Rust 官方周报 390 期(2021-05-12)
  25. [Rust] Rust + Android 的集成开发设计
  26. [Rust] Rust 1.52.1 已正式发布,及其新特性详述
  27. [Rust] 让我们用 Rust 重写那些伟大的软件吧
  28. [Rust] Rust 1.52.0 已正式发布,及其新特性详述
  29. [Rust] Rust 官方周报 389 期(2021-05-05)
  30. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务(4) - 变更服务,以及小重构
  31. [Rust] Rust 1.52.0 稳定版预发布测试中,关键新特性一瞥
  32. [Rust] Rust 生态中,最不知名的贡献者和轶事
  33. [Rust] Rust 基金会迎来新的白金会员:Facebook
  34. [Rust] Rustup 1.24.1 已官宣发布,及其新特性详述
  35. [Rust] Rust 官方周报 388 期(2021-04-28)
  36. [Rust] Rust 官方周报 387 期(2021-04-21)
  37. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(4)- 变更服务,以及第二次重构
  38. [Rust] Rustup 1.24.0 已官宣发布,及其新特性详述
  39. [Rust] basedrop:Rust 生态中,适用于实时音频的垃圾收集器
  40. [Rust] Rust 编译器团队对成员 Aaron Hill 的祝贺
  41. [Rust] Jacob Hoffman-Andrews 加入 Rustdoc 团队
  42. [机器人] 为什么应将 Rust 引入机器人平台?以及机器人平台的 Rust 资源推荐
  43. [Rust] rust-lang.org、crates.io,以及 docs.rs 的管理,已由 Mozilla 转移到 Rust 基金会
  44. [Rust] Rust 官方周报 386 期(2021-04-14)
  45. [Rust] Rust 编译器(Compiler)团队 4 月份计划 - Rust Compiler April Steering Cycle
  46. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务(3) - 重构
  47. [Rust] 头脑风暴进行中:Async Rust 的未来熠熠生辉
  48. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务(2) - 查询服务
  49. [GraphQL] 基于 actix-web + async-graphql + rbatis + postgresql / mysql 构建异步 Rust GraphQL 服务 - 起步及 crate 选择
  50. [Rust] Rust 2021 版本特性预览,以及工作计划
  51. [Rust] Rust 用在生产环境的 42 家公司
  52. [Rust] 构建最精简的 Rust Docker 镜像
  53. [Rust] Rust 官方周报 385 期(2021-04-07)
  54. [Rust] 使用 Rust 做异步数据采集的实践
  55. [Rust] Android 支持 Rust 编程语言,以避免内存缺陷
  56. [Rust] Android 平台基础支持转向 Rust
  57. [Rust] Android 团队宣布 Android 开源项目(AOSP),已支持 Rust 语言来开发 Android 系统本身
  58. [Rust] RustyHermit——基于 Rust 实现的下一代容器 Unikernel
  59. [Rust] Rustic:完善的纯粹 Rust 技术栈实现的国际象棋引擎,多平台支持(甚至包括嵌入式设备树莓派 Raspberry Pi、Buster)
  60. [Rust] Rust 迭代器(Iterator trait )的要诀和技巧
  61. [Rust] 使用 Rust 极致提升 Python 性能:图表和绘图提升 24 倍,数据计算提升 10 倍
  62. [Rust] 【2021-04-03】Rust 核心团队人员变动
  63. [Rust] Rust web 框架现状【2021 年 1 季度】
  64. [Rust] Rust 官方周报 384 期(2021-03-31)
  65. [Rust] Rust 中的解析器组合因子(parser combinators)
  66. [生活] 毕马威(KPMG)调查报告:人工智能的实际采用,在新冠疫情(COVID-19)期间大幅提升
  67. [Python] HPy - 为 Python 扩展提供更优秀的 C API
  68. [Rust] 2021 年,学习 Rust 的网络资源推荐(2)
  69. [Rust] 2021 年,学习 Rust 的网络资源推荐
  70. [生活] 况属高风晚,山山黄叶飞——彭州葛仙山露营随笔
  71. [Rust] Rust 1.51.0 已正式发布,及其新特性详述
  72. [Rust] 为 Async Rust 构建共享的愿景文档—— Rust 社区的讲“故事”,可获奖
  73. [Rust] Rust 纪元第 382 周最佳 crate:ibig 的实践,以及和 num crate 的比较
  74. [Rust] Rust 1.51.0 稳定版本改进介绍
  75. [Rust] Rust 中将 markdown 渲染为 html
  76. [生活] 国民应用 App 的用户隐私数据窥探
  77. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(3)- 重构
  78. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(2)- 查询服务
  79. [GraphQL] 构建 Rust 异步 GraphQL 服务:基于 tide + async-graphql + mongodb(1)- 起步及 crate 选择
  80. [Rust] Rust 操控大疆可编程 tello 无人机

Topics

rust(80)

graphql(17)

rust-官方周报(17)

webassembly(15)

async-graphql(9)

wasm(9)

rust-官方博客(8)

yew(8)

tide(7)

rust-web(7)

this-week-in-rust(6)

mysql(5)

rbatis(5)

android(4)

actix-web(4)

mongodb(3)

json-web-token(3)

jwt(3)

cargo(3)

技术延伸(3)

rust-wasm(3)

trunk(3)

handlebars(3)

rhai(3)

用户隐私(2)

学习资料(2)

python(2)

ai(2)

人工智能(2)

postgresql(2)

rust-compiler(2)

rust-基金会(2)

rust-foundation(2)

rustup(2)

rust-toolchain(2)

rust-工具链(2)

rust-游戏开发(2)

rust-区块链(2)

graphql-client(2)

rust-game(2)

tello(1)

drone(1)

无人机(1)

隐私数据(1)

markdown(1)

html(1)

crate(1)

async(1)

异步(1)

旅游(1)

不忘生活(1)

葛仙山(1)

hpy(1)

python-扩展(1)

正则表达式(1)

解析器组合因子(1)

组合器(1)

regular-expression(1)

parser-combinator(1)

regex(1)

官方更新(1)

rust-工作招聘(1)

rust-技术资料(1)

rust-周最佳-crate(1)

rust-web-框架(1)

rust-web-framework(1)

rust-核心团队(1)

rust-core-team(1)

rust-language-team(1)

pyo3(1)

rust-python-集成(1)

python-性能改进(1)

迭代器(1)

iterator-trait(1)

国际象棋(1)

chess(1)

游戏引擎(1)

game-engine(1)

虚拟化(1)

unikernel(1)

rustyhermit(1)

linux(1)

virtualization(1)

sandboxing(1)

沙箱技术(1)

数据采集(1)

异步数据采集(1)

docker(1)

镜像(1)

生产环境(1)

rust-评价(1)

rust-2021-edition(1)

rust-2021-版本(1)

graphql-查询(1)

vision-doc(1)

愿景文档(1)

代码重构(1)

steering-cycle(1)

方向周期(1)

隐私声明(1)

机器人(1)

robotics(1)

rustdoc(1)

rust-编译器(1)

实时音频(1)

real-time-audio(1)

变更服务(1)

mutation(1)

查询服务(1)

query(1)

rust-贡献者(1)

rust-轶事(1)

rust-稳定版(1)

rust-预发布(1)

rust-测试(1)

安全编程(1)

可信计算(1)

安全代码(1)

secure-code(1)

rust-android-integrate(1)

rust-embedded(1)

rust-嵌入式(1)

rust-生产环境(1)

rust-2021(1)

rust-production(1)

网页快照(1)

网页截图(1)

水印效果(1)

图片水印(1)

yew-router(1)

css(1)

web-前端(1)

wasm-bindgen(1)

区块链(1)

blockchain(1)

surf(1)

dotenv(1)

标识符(1)

rust-1.53.0(1)

rusthub(1)

Elsewhere

- Open Source
  1. github/zzy
  2. github/sansx
- Awesome Blog
  1. Sansx's Studio
  2. 曙光磁铁的博客
- Learning & Studying
  1. Rust 学习资料 - 芽之家