Python Pillow 优化,打开和保存速度最快提高14倍

Tachyon分析器

我试用了Python 3.15中新增的"高频统计采样分析器" Tachyon ,看看能否提升 Pillow 图像处理库的运行速度。我首先编写了一个简单的脚本来打开图像:

python 复制代码
import sys
from PIL import Image

im = Image.open(f"Tests/images/hopper.{sys.argv[1]}")

然后运行:

console 复制代码
$ python3.15 -m profiling.sampling run --flamegraph /tmp/1.py png
Captured 35 samples in 0.04 seconds
Sample rate: 1,000.00 samples/sec
Error rate: 25.71
Flamegraph data: 1 root functions, total samples: 26, 169 unique strings
Flamegraph saved to: flamegraph_97927.html

由此生成以下火焰图:

整个过程耗时 40 毫秒,其中一半在 Image.pyopen() 中完成。如果您访问交互式 HTML 页面,我们可以看到 open() 调用 preinit(),而 preinit() 又导入了 GifImagePluginBmpImagePluginPngImagePluginJpegImagePlugin(将鼠标悬停在 <module> 框上即可查看它们)。

我们只需要 PNG 格式,真的需要导入所有这些插件吗?

好的,我们来尝试另一种类型的图像:

console 复制代码
$ python3.15 -m profiling.sampling run --flamegraph /tmp/1.py webp
Captured 59 samples in 0.06 seconds
Sample rate: 1,000.00 samples/sec
Error rate: 22.03
Flamegraph data: 1 root functions, total samples: 46, 256 unique strings
Flamegraph saved to: flamegraph_98028.html

嗯,耗时 60 毫秒,其中 80% 的时间在 open() 中,而这 80% 的时间又大部分在 init() 中。HTML页面显示它导入了 AvifImagePluginPdfImagePluginWebpImagePluginDcxImagePluginDdsImagePluginPalmImagePlugin。此外,preinit 还导入了 GifImagePluginBmpImagePluginPngImagePlugin

再说一遍,既然我们只关心 WebP,为什么还要导入更多插件呢?

正在加载所有插件?

分析就到此为止,我们来看看代码。

当对图像进行open()save()处理时,如果 Pillow 尚未初始化,我们会调用一个preinit()函数。该函数通过导入插件来加载五种格式的驱动程序:BMP、GIF、JPEG、PPM 和 PNG。

导入过程中,每个插件都会注册其文件扩展名、MIME 类型以及一些用于打开和保存的方法。

然后我们依次检查每个插件,看看哪个插件可以接受这张图片。Pillow 的大多数插件都是通过打开文件并检查前几个字节是否匹配某个特定的前缀来检测图片的。例如:

  • GIFb"GIF87a"b"GIF89a" 开头。
  • PNGb"\211PNG\r\n\032\n" 开头(参考)。
  • JPEGb"\xff\xd8\xff" 开头,其中 \xff\xd8 表示"图像的开始",\xff 表示下一个标记的开始(参考)。

如果这五个插件都不符合条件,我们就调用init(),它会导入剩余的 42 个插件。然后,我们逐个检查这些插件是否匹配。

这种情况至少从 2000 年发布的PIL 1.1.1版本开始就存在(这是我能查到的最早版本)。当时有 33 个内置插件,现在是 47 个。

懒加载

如果一个程序在其生命周期内只需要一两种图像格式,那么这样做就有点浪费了,尤其是对于命令行界面(CLI)之类的程序。运行时间较长的程序可能需要更多格式,但不太可能需要全部 47 种。

插件系统的一个好处是第三方可以创建自己的插件,但我们可以使用内置功能提高效率。

我提交了一个PR ,添加了文件扩展名到插件的映射。在调用 preinit()init() 之前,我们可以先进行简单的查找,这样可以省去导入、注册和检查所有这些插件的步骤。

当然,我们可能会遇到没有扩展名或扩展名"错误"的图像,但这没关系;我估计这种情况很少见,而且无论如何我们都会回退到原来的 preinit() -> init() 流程。

合并 PR 后,以下是用于打开 PNG( HTML 页面)的新火焰图:

对于 WebP( HTML 页面):

火焰图的宽度已缩放至相同,但方框数量大大减少,这意味着现在的工作量也大大降低。处理时间从 40 和 60 毫秒降至 20 和 20 毫秒。

该 PR 包含一系列基准测试,结果显示,打开 PNG 图片(之前需要加载 5 个插件)的速度提升了 2.6 倍。打开 WebP 图片(之前需要加载全部 47 个插件)的速度提升了 14 倍。同样,保存 PNG 图片的速度提升了 2.2 倍,保存 WebP 图片的速度提升了 7.9 倍。太棒了!这些改进将在 Pillow 12.2.0 版本中实现。

参考

  • Henry Schreiner 谈如何加快包装速度
  • Adam Johnson 的tprof是另一个对这类事情很有用的新工具。

https://hugovk.dev/blog/2026/faster-pillow/

https://mp.weixin.qq.com/s/QNiQEC6U3Ykz-x4PJGiacA

相关推荐
TechWayfarer11 小时前
IP归属地API实战指南:用IP数据云解析日志挖掘用户地域分布
大数据·开发语言·网络·python·tcp/ip
Cloud_Shy61811 小时前
Python 数据分析基础入门:《Excel Python:飞速搞定数据分析与处理》学习笔记系列(第十一章 Python 包跟踪器 中篇)
数据库·python·sql·数据分析·excel·web
之歆11 小时前
DAY_13DOM操作完全指南DOM基础API与节点操作(上)
开发语言·前端·javascript·ecmascript
lsx20240611 小时前
Vue3 表单深度解析
开发语言
欢璃11 小时前
笔试强训练习
java·开发语言·jvm·数据结构·算法·贪心算法·动态规划
端平入洛11 小时前
Python 可变对象与引用穿透:为什么改了"里面的东西"外面也变了?
python
woon11 小时前
从“涂掉红色”到“删除 PDF 对象”:一次 PDF 去印章脚本改造实践
python
花开·莫之弃11 小时前
Mac安装多版本jdk(jenv)
java·开发语言·macos
qq_4017004111 小时前
Qt 自定义无边框窗口:标题栏、拖拽移动与缩放
开发语言·qt
fish_xk11 小时前
c++11的初见
开发语言·c++·算法