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

相关推荐
kruptos5 小时前
分布式系统怎么“选主“?Raft 共识一次讲清
开发语言·分布式·php·共识算法
吴声子夜歌6 小时前
ApacheCommons——commons-cli(命令行参数解析)
java·开发语言·apache
benchmark_cc6 小时前
REST API 和 Python SDK 应该怎么选?量化交易数据接口选型实战
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
王的宝库7 小时前
Go 项目结构:从单文件到标准工程布局
开发语言·后端·golang
Tairitsu_H7 小时前
[C++] 深入理解红黑树:封装set与map
开发语言·c++·set·map·红黑树·模拟实现
泡海椒7 小时前
JQuick-Curl 性能分析:并发场景下的性能表现与调优,第三方接口调用不只要快写也要稳跑
java·开发语言·okhttp
小智老师PMP7 小时前
2026深度解析|PMP第八版与NPDP核心侧重点本质区别(管理类证书怎么选)
开发语言·分布式·算法·职场和发展·产品经理
2601_962382438 小时前
python快乐编程网络爬虫课后答案
python·网络爬虫·学习工具·课后答案·刷题app
鹏哥带你干乡墅8 小时前
优选乡墅赋能培训平台如何帮助提升乡村建设?
大数据·人工智能·python
江畔柳前堤8 小时前
前台·中台·后台:2026年AI原生时代的架构全景图
开发语言·人工智能·算法·机器学习·架构·scala·ai-native