io.BytesIO 很慢。备择方案?优化?

io.BytesIO is very slow. Alternatives? Optimizations?

我正在 运行使用相机在 Raspberry Pi 上安装 Python v3.5 脚本。该程序涉及从 picamera 录制视频并从视频流中获取样本帧以对其执行操作。有时,处理字节缓冲区需要很长时间(20 多秒)。包含问题区域的代码的简化版本是:

import io
import picamera

camera = picamera.PiCamera()
camera.start_recording("/path/to/file.h264")
cnt = 0
while True:
    if cnt > 30:
        stream = io.BytesIO()
        camera.capture(stream, use_video_port=True, resize=(1920, 1080), format='rgba')
        cnt = 0
    else:
        cnt += 1

一段时间后,打开字节流的时间变得疯狂起来。在我最近的 运行 中,一个实例耗时超过 48 秒! This figure shows a plot of the times to open the byte stream for each cycle. 我对代码问题区域的每一行进行了计时测试,我可以确认是 stream = io.BytesIO() 行导致了延迟。

当我在这个任务中使用 psutils 监视 CPU 和 Raspberry Pi 的内存时,我没有发现明显的问题。 CPU 使用率为 10-15%,虚拟内存使用率约为 24.2%,正在使用 0 交换空间。

除了 Python 程序外,没有其他用户执行的进程 运行 在 Pi 上运行。硬件 运行 正在使用 GUI 默认 Raspbian 安装。

由于 Python 程序有 1000 多行,我不会在这个问题文本中包含任何超出最小示例的内容。如果您想查看上下文信息,please take a look at this Gist with the code.

初步搜索表明这是 BytesIO 的一个已知问题。 Python 的一些旧错误跟踪(约 2014 年)表明在 3.5 版本中对某些情况进行了改进。

问题是:

编辑:我在循环中添加了一行,使用 stream.close() 强制流在每个进程结束时关闭,但这似乎无效。我的流打开时间仍然超过 20 秒。

EDIT_2:我从编辑的信息中读错了测试中的值,错过了这些值有科学计数法。

循环调用BytesIO时,必须手动关闭

在示例中,由于 Python 处理关闭字节流的方式,BytesIO 看起来很慢。来自 the documentation for BytesIO:

A stream implementation using an in-memory bytes buffer. It inherits BufferedIOBase. The buffer is discarded when the close() method is called.

为什么大多数用户永远不会看到这个

在退出时发出命令之前,通常不会销毁字节缓冲区。当 Python 脚本完成并解构环境时,iobase_exit (see line 467) 发出自动关闭 ()。可以假设大多数用户只是在缓冲区中打开一个字节流并保持打开状态直到脚本完成。也许这不是 "best" 的实现方式,但这是我见过的实现 io 的大多数脚本使用它的方式。

当重复调用新流而不关闭时,缓冲区似乎不断堆积,偶尔需要系统在内存限制时协商关闭它们。 Raspberry Pi 的有限资源似乎加剧了这种情况。 这可能可以通过做一些奇特的事情来绘制缓冲区填满时的内存使用情况来衡量,但我在这里并不真正关心它,这超出了我的经验水平。

顺序使用!=重入

如果稍后重新进入 SAME 缓冲区,则情况不应如此。通过发出运行时错误,IO class 免受这种边缘情况的影响。参见 here。这与我在原始问题中报告的情况不同,因为每次调用 BytesIO 时都会生成一个新缓冲区。讨论这个问题是相关的,因为对文档这一部分的误解促成了问题中描述的事件。

OP 中 MWE 的更正

import io
import picamera

camera = picamera.PiCamera()
camera.start_recording("/path/to/file.h264")
cnt = 0
while True:
    if cnt > 30:
        stream = io.BytesIO()
        camera.capture(stream, use_video_port=True, resize=(1920, 1080), format='rgba')
        stream.close()
        cnt = 0
    else:
        cnt += 1