io.BytesIO очень медленный. Альтернативы? Оптимизации? - PullRequest
0 голосов
/ 22 марта 2019

Я запускаю скрипт Python v3.5 на Raspberry Pi с камерой.Программа включает в себя запись видео с 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 во время этой задачи, используя psutils, я не вижу никаких очевидных проблем.Загрузка ЦП составляет 10-15%, виртуальной памяти - ~ 24,2%, используется подкачка 0.

Кроме программы Python, на Pi не выполняются никакие другие пользовательские процессы.На оборудовании установлена ​​стандартная Raspbian установка с графическим интерфейсом.

Поскольку программа Python содержит более 1000 строк, я не собираюсь включать в этот текст вопроса ничего, кроме минимального примера.Если вы хотите посмотреть на него контекстную информацию, , пожалуйста, взгляните на этот список с кодом .

Предварительные поиски показывают, что это известная проблема с BytesIO.Некоторые старые исправления ошибок (около 2014 г.) для Python предполагают, что это было улучшено в некоторых случаях в версии 3.5.

Вопросы:

  • Почему BytesIO медленный здесь?
  • Есть ли альтернативный способ потоковой передачи байтов, который быстрее?
  • Есть ли лучший способ использовать BytesIO, чтобы получить то, что мне нужно?

РЕДАКТИРОВАТЬ: я добавил строку в цикл, заставляя поток закрываться в конце каждого процесса, используяstream.close(), но, похоже, это было неэффективно.У меня все еще было время открытия потока 20+ секунд.

EDIT_2: я неправильно прочитал значения в тесте из отредактированной информации и пропустил, что значения имели научное обозначение.

1 Ответ

0 голосов
/ 26 марта 2019

Когда BytesIO вызывается в цикле, он должен быть закрыт вручную.

В этом примере BytesIO выглядит медленным из-за того, как Python обрабатывает закрытие потока байтов. С документация для BytesIO :

Потоковая реализация, использующая буфер байтов в памяти. Наследует BufferedIOBase. Буфер отбрасывается, когда метод close () называется.

Почему большинство пользователей никогда не увидят это

Буфер байтов обычно не уничтожается до тех пор, пока команда не будет выполнена при выходе. Когда скрипт Python завершен и среда деконструирована, автоматически вызывается close () с помощью iobase_exit ( см. Строку 467 ). Можно предположить, что большинство пользователей просто открывают поток байтов в буфере и оставляют его открытым до завершения сценария. Возможно, это не самый лучший способ сделать это, но именно так большинство сценариев, которые я видел, которые реализуют io, используют его.

Когда новые потоки вызываются многократно без закрытия, буферы, похоже, продолжают накапливаться, иногда требуя от системы согласования закрытия их при ограничении памяти. Ограниченные ресурсы Raspberry Pi, кажется, усугубляют это. Это можно измерить, выполнив некоторые причудливые действия для составления графика использования памяти по мере заполнения буфера, но мне здесь все равно, и это за пределами моего уровня опыта.

Последовательное использование! = Повторный вход

Это не должно иметь место, если один и тот же буфер повторно вводится позднее. Класс IO защищен от этого крайнего случая путем выдачи ошибки времени выполнения. Смотрите здесь . Это отдельный случай, о котором я сообщал в первоначальном вопросе, поскольку каждый раз при вызове BytesIO создается новый буфер. Уместно обсуждать это, поскольку неправильное толкование этого раздела документации ускорило события, описанные в вопросе.

Исправление 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
...