Домашняя страница О пользе асинхронного чтения данных
Публикация
Отменить

О пользе асинхронного чтения данных

В одной игре мне понадобилось сделать проигрывание видео. Для реализации задачи, я использовал связку Ogg/Theora/Vorbis. Во-первых, потому что это бесплатные форматы, имеющие открытые библиотеки, а во-вторых, потому что их использование дает широкие возможности по работе с видео. Например легко можно создавать видео-материалы и проигрывать видео на полигонах моделей.

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

После первичной реализации плеера видео в игре и оптимизации через пиксельные шейдеры, игра проигрывала HD видео на моей машине более чем с 1200fps (кадров в секунду). Первая опасность, которая в этом кроется и о которой я хочу упомянуть - это высокий fps. Дело в том, что такая частота отрисовки может сжечь видеокарту при длительном использовании. У меня экран периодически начинал моргать, появлялись секундные задержки, а один раз весь экран покрылся разноцветными пикселями. Поэтому частоту отрисовки нужно ограничивать, допустим 512-я кадрами в секунду. Для PlayStation3 есть даже требование, чтобы fps не превышал 60.

Для примера я покажу результаты проигрывания видео из игры Need for Speed: Most Wanted.

Первичная реализация была однопоточной и делала следующее:

  1. Если нужен новый кадр, то считываем из файла кусок данных
  2. Пытаемся из него сделать кадр. Если данных не достаточно, то читаем еще данные из файла
  3. Рисуем последний построенный кадр

Поскольку видео файл может быть большим, то он не грузится целиком в память, а подгружается по мере необходимости. Это минимизирует начальную задержку при старте видео и не сильно нагружает память. Пункты 1 и 2 выполняются с частотой видео, в то время как пункт 3 выполняется в каждом кадре.

На глаз - видео играется прекрасно, и fps высокий. На этом месте можно было бы остановиться, но я продолжил профилировать и получил следующую картину:

Видим, что профайлер показывает как 4.5ms-5ms (миллисекунды) тратится на обычный кадр, но периодически возникает задержка до 30ms, когда данные считываются из файла синхронно с рисованием видео в главном потоке игры. Это крайне плохой результат. Нестабильный fps в играх может создавать неприятные ощущения пользователя, причем тот даже не поймет в чем дело.

Правильное решение очевидно - нужно стримить данные из файла в отдельном потоке. Этим очень часто пренебрегают считая, что “и так сойдет”, однако реализовать подобное не так уж сложно. В моем случае это не потребовало реорганизации кода, только вынесения функции формирования данных кадра в отдельный поток. Эта простая реорганизация кода с тестированием заняла 2 часа. В результате профайлер показал, что на кадр тратится примерно 4.0ms (смотри картинки ниже) с очень маленьким разбросом. Это говорит о стабильности алгоритма и реализации.

Вместо вывода:

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

Публикация защищена лицензией CC BY 4.0 .