4.19. Пулы памяти#
Камера, которая удерживает три полноразмерных кадра в пуле буфера кадра, параллельно использует отдельный буфер предпросмотра и при этом ещё оставляет место для скрипта на Python и его объектов, оперирует большим объёмом памяти, чем мог бы предоставить один блок RAM на MCU. MicroPython умещает всё это, распределяя данные по нескольким различным видам памяти, которые предоставляет MCU, и направляя каждый тип выделения именно в тот вид памяти, который ему действительно нужен.
4.19.1. Виды памяти#
Современный MCU камеры OpenMV Cam предоставляет четыре различных вида памяти. Первый невидим для приложения; остальные три являются пулами, из которых могут производиться выделения.
Кэш данных CPU – небольшая, очень быстрая область памяти, расположенная между CPU и остальной RAM. Когда CPU читает или записывает значение из основной памяти, кэш автоматически сохраняет его копию, поэтому повторные обращения к тем же данным остаются в кэше и не платят за выход в более медленную память. Кэш не является пулом, из которого производятся выделения. Он прозрачен для приложения – он просто делает остальную RAM на практике быстрее, чем можно было бы ожидать по её необработанной задержке, до тех пор пока рабочий набор данных в него помещается.
Тесно связанная с процессором память – небольшой блок RAM, подключённый напрямую к CPU без шины между ними. Доступ за один такт, без промахов, без ожиданий. Из этого пула производятся выделения, которым действительно нужна самая быстрая возможная память – где важен каждый такт задержки.
Быстрая встроенная память – от нескольких сотен килобайт до примерно мегабайта RAM, встроенной в корпус MCU. Низкая задержка, высокая пропускная способность, но ограниченный размер. Здесь находится куча MicroPython, чтобы доступ к объектам Python оставался быстрым; меньшие рабочие буферы, к которым CPU обращается часто, делят этот пул.
Более медленная объёмная память – на платах, где MCU сочетается с внешним кристаллом памяти, десятки мегабайт внешней RAM, доступной через внешнюю шину. Намного больше по объёму, но каждый доступ занимает больше времени, чем во встроенной памяти; кэш данных скрывает значительную часть этой стоимости для рабочих наборов, которые он может удержать, и разрыв проявляется на операциях, которые проходят по данным, слишком большим для кэширования. Используется для выделений, которые должны быть большими и при которых CPU может терпеть меньшую скорость – в первую очередь для пула буфера кадра.
Платы семейства располагаются в широком диапазоне: у некоторых есть только встроенная RAM; некоторые сочетают встроенную RAM с гораздо большим внешним блоком. Каждый из трёх выделяемых видов рассматривается как пул памяти – блок, из которого производятся выделения – и помечен так, чтобы каждый запрос мог запросить именно тот вид памяти, который ему действительно нужен.
4.19.2. Основной буфер кадра#
Буфер кадра, обеспечивающий работу snapshot(), не запрашивает быструю память. Он запрашивает достаточно памяти – не более того. Это помещает его в тот пул, который является самым большим, поэтому на плате со встроенной и внешней памятью буфер кадра оказывается во внешнем блоке.
Полноразмерный буфер кадра с тройной буферизацией слишком велик, чтобы поместиться в быстрый встроенный пул на большинстве компонентов; больший пул – единственный, кто вообще способен его удержать. Кэш данных CPU скрывает значительную часть стоимости каждого доступа, когда приложение обрабатывает изображение, а механизм DMA, заполняющий буфер кадра с датчика, в любом случае поспевает за скоростью данных датчика.
Точный размер, который занимает буфер кадра, выбирается на основе текущих значений pixformat(), framesize() и количества framebuffers(); он увеличивается или уменьшается каждый раз, когда что-либо из этого меняется.
4.19.3. Вторичные буферы кадра датчиков#
Второй экземпляр CSI получает собственный буфер кадра, выделяемый из того же пула, который использует основной. Пул общий; буферы независимы. Объём, занимаемый вторичным, обычно намного меньше, чем у основного, потому что вторичные датчики работают на более низких разрешениях, поэтому дополнительная память, которую занимает второй буфер кадра, составляет небольшую долю от объёма основного.
4.19.4. Буфер кадра потока#
Буфер предпросмотра изображения – исключение. Он не выделяется ни из одного из пулов во время выполнения; это фиксированная область, зарезервированная на этапе сборки, с известным адресом и известным размером. Это держит путь предпросмотра в стороне от всех остальных выделений – область существует с момента загрузки и никогда не перемещается.
4.19.5. Куча MicroPython#
Объекты Python – переменные, списки, словари, экземпляры классов, обёртка Image, которую возвращает вызов snapshot(), каждая строка и кортеж, создаваемые приложением – находятся в куче MicroPython со сборкой мусора, которая отделена от пулов памяти камеры. Куча со сборкой мусора (GC) – это область памяти, которой MicroPython управляет сам: код на Python неявно выделяет из неё память каждый раз при создании объекта, а MicroPython периодически сканирует кучу и освобождает место, занятое объектами, на которые приложение больше не ссылается, так что приложению никогда не приходится освобождать что-либо вручную.
При загрузке для GC-кучи выделяется отдельная область, обычно размещаемая в быстрой встроенной памяти, чтобы доступ из Python оставался быстрым, с возможным переполнением в больший внешний блок на платах, которым нужен дополнительный запас для крупных структур данных.
Объект Image, возвращаемый методом snapshot(), – это небольшая обёртка в GC-куче; лежащие в его основе пиксельные данные находятся в буфере кадра в одном из пулов камеры. Эти двое никогда не конкурируют за одну и ту же память.
4.19.6. Собираем всё вместе#
Направление каждого вида выделения в нужный пул – большие буферы в больший пул, где они помещаются, чувствительные к задержке данные в более быстрые пулы, куча Python в собственную область, предпросмотр в свой зарезервированный слот – это и есть то, что позволяет одновременно запускать полноразмерный конвейер захвата, канал предпросмотра и нетривиальный скрипт на Python на компонентах, у которых в сумме всего несколько мегабайт быстрой памяти.