Куда уходят миллисекунды? Способ повышения FPS

Из серии «Моя борьба с микроконтроллерами»
Куда уходят миллисекунды? Способ повышения FPS
Where the milliseconds are lost? FPS : the way to rise up


Статья предназначена для людей, начинающих знакомиться с микроконтроллерами

В выпуске:
Микроконтроллеры и «тяжелые» приложения
Постановка задачи
Используемое аппаратное обеспечение и настройки
Тестовый материал
Проверка производительности. Куда уходят миллисекунды? Повышаем FPS
Выводы: не так все безнадежно



Микроконтроллеры и «тяжелые» приложения

Хорошую и нужную программу можно сделать на любом языке. Но универсальность подхода языков высокого уровня, позволяющая легко инкорпорировать уже разработанные решения, наличие постоянно увеличивающихся ресурсов в виде памяти и быстродействия, быстро развращают неокрепшие умы и своими программами они только подхлестывают гонку ресурсов. Отдельное спасибо за это Microsoft-у.

Конечно, ничего плохого в улучшении аппаратной оснащенности нет, но в стародавние времена, программируя на любом языке, все же больше старались выжать максимум из имеющегося железа.

Проблема аккуратного и полноценного использования ресурсов приобретает особое значение, когда создаются приложения для микроконтроллеров, особенно для их нижнего сегмента по производительности. Принято считать, что для работы с мультимедийным контекстом (за исключением звука) — воспроизведением видео, 3D приложениями и подобной тяжелой нагрузки должны применяться «тяжеловесы» микроконтроллеростроения — Cortex-Axx или «applicaton MCU» ARM 9xx, работающие на частотах минимум в несколько сот мегагерц. Но производительность 72МHz Cortex-M3 вполне сопоставима с i386, так ли они беспомощны на самом деле? Попробуем это выяснить.

Постановка задачи

Давайте подсчитаем, что мы можем ожидать от 72МHz Cortex-M3 при выводе изображения на экран 320х240 пикселов. Этот размер уже считается неприличным для всякого рода развлекательных гаджетов, но он все еще широко распространен.
Можно было бы добавить «потокового вывода», но полноценно это мы будем оценивать (пока ) виртуально, так как в приведенном ниже тесте DMA не использовалась. Все же мы определим предельное время, которое МК сможет потратить на пре/пост-обработку, с учетом работы DMA.

Всего экранных точек 320х240 = 76800

Поскольку примененный LCD не поддерживает 24-битный цвет, (максимально — 18-битный), то будет удобно, с точки зрения нагрузки на шину, ограничиться сверху 16-битным цветом. Это означает, что каждая точка изображения требует двух байт. При этом следует различать формат хранения (например, bmp, png и т.д. с глубиной цвета от 1 до 32bpp и выше, когда информация о цвете кодируется по определенному алгоритму), от формата, требуемого для LCD. LCD принимает определенный в документации дисплея RGB формат цвета пиксела (и для «черно-белых» изображений тоже), очищенный от контейнерной оболочки. То есть, и 24bpp bmp-файл 320х240 размером 225 килобайт, и 1bpp bmp-файл 320х240 размером 9.43 килобайт потребуют, в общем случае, одинакового количества циклов передачи данных в LCD. Различия будут в алгоритме извлечения из контейнера требуемой RGB-последовательности и его реализации, и в разном времени доступа к носителю, на котором содержится данный файл (или его raw-эквивалент, то есть данные изображения без стандартных заголовков для конкретного формата, паддинговых байтов и т.д.).

Таким образом, для одного кадра LCD потребуется 320х240х2 = 153600 байт информации. Если считать, что для динамического изображения нам хватит 25 кадров в секунду (на самом деле для таких разрешений достаточно будет и 15-20), то наша система должна обеспечивать запись в LCD 3.75 мегабайт в секунду, без учета дополнительной нагрузки в виде считывания файла и его обработки, вывода аудио контента и других параллельных задач. Поскольку в реальных устройствах будет применяться DMA, облегчающая распараллеливание, то возможность подобного полноэкранного вывода будет обеспечена, если для каждого фрейма время получения данных с носителя и их обработки не будет превышать время потоковой передчи данных фрейма c использованием DMA. Следовательно, нам важно для каждого фрейма сопоставить пропускную способность интерфейса LCD, время получения данных и время обработки данных до передачи в LCD.

Используемое аппаратное обеспечение и настройки

В качестве тестовой платформы используется платка собственного дизайна ZP-STM32 с МК STM32F103C8 (64KB Flash, 20KB SRAM), работающем на тактовой частоте 72MHz.




Подробнее про ZP-STM32 написано здесь.

LCD - Multi-Inno MI0283QT-2, также на плате собственного дизайна, на обратной стороне которой находятся два микропереключателя по восемь контактных групп для выбора интерфейса, режимов работы и контроллер тачскрина AR1020. Подключен LCD по последовательному интерфейсу SPI (на шину APHB1), с максимальной частотой 18MHz. Это определяет предельную теоретическую пропускную способность в 2 MBps, реально- 1.1-1.5 MBps, что раза в три ниже рассчитанной выше полосы. Последовательный интерфейс используется исключительно по причине нехватки выводов МК для полноценного параллельного 16-битного интерфейса, поэтому облегчим задачу. Будем использовать не QVGA разрешение, а ¼ от QVGA, то есть 160х120 пикселов, а производительность на QVGA экране получим интерполированием. Хотя это и не совсем правильно, для примерной оценки сгодится.

«Виртуальный экран» будет содержать 160х120 = 19200 пикселов, требуемая пропускная способность интерфейса при этом 937.5 килобайт в секунду (для 24bpp) и последовательный интерфейс даже будет иметь запас выше 10%. К тому же, на экране 320х240 можно будет разместить четыре «виртуальных экрана».

8bpp поток нагрузит шину примерно на 333 KBps, а 1bpp немногим более 40 KBps.
Отметим, что наш суррогатный видеопоток для 24bpp превосходит тот объем данных, что идет по оптимизированному кодированному видеопотоку даже для больших разрешений экрана, поэтому мы явно перегружаем систему. Но тяжело в учении - легко в бою.


В качестве накопителя будет использоваться SDHC Sandisk 4GB, замеренное быстродействие на устоявшихся операциях чтения 5.8MBps (на ПК), на MK установлена файловая система FatFS версии 0.08 с небольшими доработками, статический буфер под дисковые операции 10 килобайт. Интерфейс для SD - SPI (на шине APHB2).

В этом тесте не будет использоваться «внутренняя» флэш-память, в виде отдельных по микросхем с последовательным или параллельным интерфейсом, которая может обеспечить потенциально более высокое общее быстродействие (так как не требуется файловой системы и информация может храниться в «raw» формате, пригодном для непосредственной передачи в LCD без обработки), это мы отложим для следующего раза. Так как хочется проверить и спецрежимы некоторых флэшек, например, от Amic c их ПСП в 200MBps для 16мбитных и 400MBps для QSPIFI (под Cortex-M4), но это требует нестандартного управления и формата данных.

Последовательные шины SPI2 и SPI1 находятся в эксклюзивном владении LCD и SDHC соответственно (прочие устройства на период тестов имеют высокий уровень на своих CS входах). Работает только системное прерывание SYSTICK с периодом в одну миллисекунду для возможности измерения времени (хотя лучше установить разрешение по времени 100 микросекунд, но тут тоже компромисс).

Программа использует стандартные библиотеки периферии STM версии 3.3.0, написана на языке С в среде Atollic True Studio Lite 1.4, отладчик ST-Link.

Тестовый материал

Поскольку я только приступил к изучению 32-битных микроконтроллеров, то детально не вникал, имеются ли для семейства STM32 BSP-модули с подходящим функционалом. По крайней мере, в апнотах и материалах от STM нет библиотек работы с потоковым видео, пригодным для применения, а перерабатывать стандартные пока желания не возникло. Поэтому стандартные потоки пока обрабатывать нечем, сделаем свой тестовый материал сами.

Для оценки скорости файловой системы и буфера, а также обработки для приведения данных к требуемому RGB-формату, нам потребуется исходный материал в 24-битном, 8-битном и однобитном цвете (c дизерингом, просто из любопытства посмотреть вживую на такую однобитную анимацию).

Современных креативных инструментов я не знаю, да и особо они мне не нужны, вспоминаю про Particle Illusion, освежаем в памяти, какие кнопки нажимать и за три минуты создаем 50 кадров двухсекундного мультика, будем использовать только 40 кадров, чтобы исходное положение мышонка Габриэля совпадало с конечным. Архив «мультика» можно скачать отсюда (это, для экономии места, 8-битка, размер 385 килобайт). Быстро посмотреть анимацию можно через встроенную виндовскую смотрелку - надо открыть первую картинку в папке, затем нажать и удерживать стрелку «вправо» на клавиатуре.




Мышонок будет шагать, постоянно меняясь в размерах, огни переливаться, а водопад, естественно, водопадать. Листва тоже движется, но это не так заметно.

Конвертируем исходную последовательность, чтобы иметь варианты с 24, 8 и 1-битным цветом.




Напомним, что в формате BMP для разрешения 160х120 с 24bpp размер файла будет 57654 байта, при 8bpp — 20278 байт и для 1bpp — 2462 байта.

Добавим в тест фрагмент художественного фильма, это будет кусочек в 999 кадров из четвертой серии «17 мгновений весны». Сначала вместо фрагмента фильма был черно-белый вариант мультика, но так как по быстродействию это равнозначно 8-битному цвету, то для разнообразия сюжета вставлен фильм.

Проверка производительности. Куда уходят миллисекунды? Повышаем FPS

Программа для каждого «виртуального экрана» прогоняет заданное число раз указываемое число кадров, можно указать начальный кадр и количество кадров. Эти параметры индивидуальные для каждого «виртуального экрана». После окончания работы каждого экрана выводится информация в виде среднего числа FPS*100, то есть число 1000 будет говорить о среднем FPS, равном 10.

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

Сначала будем использовать построчное, под размер строки, чтение из файла (нужно учитывать, что формат BMP хранит информацию, начиная с конца картинки, при этом используются «паддинговые» байты для выравнивания строк по 32-битной границе, а для 1bpp, плюс к тому, дополнительные «паддинговые» биты для последнего байта для выравнивания по границе байта).

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

Битрейт:
24bpp – 4.01 fps
8bpp - 8.4 fps
1bpp – 12.49 fps

На самом деле без труда производительность можно поднять более, чем вдвое для 24bpp и 8bpp, и процентов на 60 для 1bpp.




Вот фотография экрана, на котором,
24bpp – 8.26 fps
8bpp - 16.12 fps
1bpp – 20.00 fps

На ролике (ссылка ниже) результаты еще лучше.

Давайте посмотрим, куда утекает и на что тратится драгоценное время.

Разумеется, буфер на одну строку это мало. Для 24bpp это 960 байт, но для МК STM32F103С8, имеющего 20 килобайт встроенной SRAM спокойно можно даже половину памяти выделить по буфер. Можно попробовать использовать и внешнюю последовательную память на 32 килобайта, что с DMA будет наверняка оправдано. На более продвинутых версиях STM32 (со встроенным контроллером статической памяти FSMC и установленной внешней памятью) размер буфера можно будет существенно увеличить и даже создать цепочку экранных буферов. STM, в апноте для подсоединения LCD по RGB интерфейсу, такой подход не рекомендует, но я таким прямолинейным советам не очень доверяю. Скоро будет возможность проверить это самому — в разработке плата ZP-STM32M, на базе STM32F103 в 144-ногом корпусе и достаточным объемом памяти.

Ставим размер буфера для чтения с SD карты 10 килобайт (512х20), то есть 24bpp читается в шесть проходов, 8bpp — в два, 1bpp – целиком, и можно даже сразу четыре полных кадра записать в буфер. Оптимально производить чтение размером, кратным сектору в использованной файловой системе.

Но это даст прирост от 30% на самом тяжелом режиме до 10-15% при 1bpp.

Теперь самое время сказать о том, что для операций чтения - записи используются стандартные функции периферийной библиотеки STM. Разумеется, эти библиотеки (как и большинство подобного материала от всех программных и «железячных» производителей) даны только для иллюстрации и в большинстве своем служат только средством обучения.

Это приводит к тому, что люди, изучающие микроконтроллеры только «через язык программирования С», на этом остановятся и будут считать, что достигли максисмума. Это неправильный подход, но в этой жизни вообще мало что делается правильно.

Типовые функция чтения и записи в порт SPI из стандартной библиотеки периферии STM выглядят так:
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data)

{
/* Check the parameters */
assert_param(IS_SPI_ALL_PERIPH(SPIx));

/* Write in the DR register the data to be sent */
SPIx->DR = Data;
}

uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx)
{
/* Check the parameters */
assert_param(IS_SPI_ALL_PERIPH(SPIx));

/* Return the data in the DR register */
return SPIx->DR;
}


Для записи в LCD порт конфигурируется на передачу 16 бит, а для чтения SD – карты на 8 бит. Получается, что для каждого пиксела или байта данных вызывается отдельная функция. Для потокового ввода/вывода это непроизводительные затраты по работе со стеком, превосходящие по времени операции записи/чтения пиксела или байта. Очевидно, что функцию нужно заменить макроподстановкой, просто используя, там, где надо для записи
SPIx->DR = Data;


а для чтения в буфер
*buffer++ = SPIx->DR;


При этом цикл записи в LCD желательно развернуть на 8-16-32 операции, это даст дополнительных 3-5% прироста.

Эти приемы используются достаточно часто, но также часто забывают и о втором компоненте, ожидающем установки аппаратного флага готовности к следующей операции. При этом, вместо прямого обращения к регистру SR МК, по-прежнему в потоке вызывается функция.

То есть, вместо пары строк для каждого пиксела
SPI_I2S_SendData(LCD_SPI, pixel);

while (SPI_I2S_GetFlagStatus(LCD_SPI, SPI_I2S_FLAG_TXE) == RESET);


Правильно будет использовать:
LCD_SPI->DR = pixel;

while ((LCD_SPI->SR & SPI_I2S_FLAG_TXE) == RESET);

Аналогично и для чтения с флагом RXNE.

И именно эта последняя строчка, уменьшающая латентность доступа к порту, и вносит наиболее весомый вклад в увеличение быстродействия.

Можно поработать и с дополнительным выравниванием структур данных.

Сказанное выше относится не только к микроконтроллерам от STM, это верно для любых типов МК.

И не забывайте о таймингах устройств. Заменив во всем своем коде вышеозначенные функции на макросы, можно легко получить неработоспособную программу. Например, если сигнал CS дисплея — макрос прямой работы с портом, при подаче низкого уровня активируется дисплей и если сразу следует макрос записи в системный регистр дисплея, то такая комбинация может и не заработать. По техническим данным для LCD MI0283QT2 (вернее, для его контроллера HX8347D), после CS должна быть пауза не менее 15 наносекунд. При такте 13.88нс (72MHz) это означает необходимость пропуска двух тактов процессора до операций с дисплеем. Так как функция до выполнения работает (внутренне) со стеком гораздо дольше, такая пауза обеспечивается. При использовании макроса необходимо после выборки по CS добавить
asm volatile("nop");


а, правильнее, но не по-оверски — двойной «nop».

Как эти анимашки выглядит вживую, можно посмотреть на ролике (4.23МБ, mp4). Качество неважное (смартфон), в реальности все смотрится гораздо красивее. Скорость камеры такая, что она ловит два кадра на экране LCD в свой один, в реальности на LCD у мышонка нет четырех ног, да и для мульта резерв есть - мышонок двигается каждый второй кадр. То есть, двигая его каждый кадр, fps увеличим вдвое .

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

На бенчмарке фильма 22.72 fps не совсем верно отражает ситуацию. Во фрагменте 999 кадров, но тогда битрейт 11 кадров с хвостиком, при битрейте оригинала 25. Поэтому показывается только каждый второй кадр, а суммарное время делится на 999 кадров. Но плавность это для экрана 160х120 не нарушает. Пока не очень понял, почему с ростом количества кадров падает fps, словно как у SD карты стоит входной буфер. На 300 кадров - 13.3, на 999 - 11.2. Учитывая, что это обычный 8bpp индексный формат. На большой новой системе попозже разберемся.


Как оказалось, для 1bpp бенчмарк делался на одном из старых вариантов, где флаг ловится функцией, а не макросом. Замена на макрос дает прирост 11%, после некоторых оптимизаций суммарно повышена производительность более, чем на 30% (по отношению к 20 fps) и сейчас результат 26.31 fps.

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

Выводы: не так все безнадежно

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

Но, в целом, учитывая что мы экспериментировали с перегруженным (избыточным) потоком и на слабом интерфейсе, очевидно, что, при желании, можно наверняка обеспечить плавный вывод на экран 16-битного цвета (параллельный интерфейс обеспечивает десятикратный рост производительности). Другое дело, хватит ли STM32 для DSP-операций по раскодированию-преобразованию, вот тут можно законно засомневаться. Но то, что этот МК проприетарный полноэкранный 320х240 формат для плавного видео вытянет, у меня сомнений нет.

На плате ZP-STM32M будет установлен сопроцессор VLSI1053, который понимает практически любой аудиоформат, мы освободим центральный МК от этой работы и бросим всю его мощь на графическую составляющую.

На сегодня все.
Если есть что сказать — то идем сюда.

Всем удачи,
Zauropod.

25 ноября 2010 года.
Telegram-канал @overclockers_news - это удобный способ следить за новыми материалами на сайте. С картинками, расширенными описаниями и без рекламы.
Оценитe материал

Возможно вас заинтересует

Популярные новости

Сейчас обсуждают