0

我在带摄像头的 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 秒! 该图显示了每个周期打开字节流的时间图。 我对代码有问题的区域中的每一行进行了时序测试,我可以确认它是stream = io.BytesIO()导致延迟的行。

当我在此任务期间使用 监控 Raspberry Pi 的 CPU 和内存时psutils,我没有观察到明显的问题。CPU 使用率为 10-15%,虚拟内存使用率约为 24.2%,并且正在使用 0 交换。

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

由于 Python 程序有 1000 多行,因此我不会在此问题文本中包含超出最小示例的任何内容。如果您想查看上下文信息,请查看此 Gist 和代码

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

问题是:

  • 为什么BytesIO这里慢?
  • 是否有另一种更快的流式传输字节的方法?
  • 有没有更好的方法来BytesIO获得我需要的东西?

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

EDIT_2:我从编辑的信息中误读了测试中的值,并错过了具有科学记数法的值。

4

1 回答 1

1

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

在示例中,由于 Python 处理关闭字节流的方式,BytesIO 似乎很慢。从BytesIO 的文档中

使用内存字节缓冲区的流实现。它继承了 BufferedIOBase。调用 close() 方法时,缓冲区将被丢弃。

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

在退出时发出命令之前,字节缓冲区通常不会被破坏。当 Python 脚本完成并解构环境时,会发出自动 close() iobase_exit参见第 467 行)。可以假设大多数用户只是在缓冲区中打开一个字节流并保持打开状态,直到脚本完成。也许这不是做到这一点的“最佳”方式,但这就是我见过的大多数脚本io使用它的方式。

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

顺序使用 != reentry

如果稍后重新进入 SAME 缓冲区,则不应出现这种情况。通过发出运行时错误来保护 IO 类免受这种边缘情况的影响。见这里。这与我在原始问题中报告的情况不同,因为每次调用 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
于 2019-03-26T16:41:35.967 回答