io.BytesIO работает очень медленно. Альтернативы? Оптимизация?

#python #camera #raspberry-pi #bytesio #bytestream

#python #камера #raspberry-pi #bytesio #байтовый поток

Вопрос:

Я запускаю скрипт 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. Что это за Raspberry Pi? Под какой ОС? Какая версия OpenCV?

2. Какой смысл считать здесь до 30? Похоже, вы не отбрасываете остальные 29 кадров….

3. RPI3 запускает Raspbian Stretch. OpenCV не используется, обработка изображений происходит полностью с помощью Numpy. Подсчет — это просто заполнитель для таймера в этом минимальном рабочем примере. В реальной версии цикл BytesIO / camera вызывается циклом pygame каждые несколько секунд.

Ответ №1:

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

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

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

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

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

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

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

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

Исправление MWE в OP

 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
  

Комментарии:

1. это делает его быстрее?

2. @DonCode Я не уверен, что это обязательно выполняется быстрее, поскольку я не проводил тест синхронизации, когда играл с этим. Однако, если вы генерируете несколько байтовых потоков, не закрывая их, в конечном итоге вы раздвинете границы того, с чем может справиться ваша система.