4.19. 메모리 풀

프레임 버퍼 풀에 세 장의 전체 해상도 프레임을 담고, 그와 별도로 프리뷰 버퍼를 함께 돌리면서도, Python 스크립트와 그 객체들을 위한 공간까지 남겨 두는 카메라는 MCU의 단일 RAM 블록이 제공할 수 있는 것보다 훨씬 많은 메모리를 다루고 있는 셈입니다. MicroPython은 MCU가 제공하는 여러 가지 서로 다른 종류의 메모리에 이를 분산시키고, 각 할당을 실제로 필요로 하는 종류의 메모리로 라우팅함으로써 이 모든 것을 담아냅니다.

4.19.1. 메모리의 종류

최신 OpenMV Cam MCU는 네 가지 서로 다른 종류의 메모리를 제공합니다. 첫 번째는 애플리케이션에게 보이지 않으며, 나머지 세 가지는 할당이 이루어지는 풀입니다.

  • CPU의 데이터 캐시 – CPU와 나머지 RAM 사이에 위치하는 작고 매우 빠른 메모리 영역입니다. CPU가 메인 메모리에서 값을 읽거나 쓸 때 캐시는 자동으로 사본을 보관하므로, 동일한 데이터에 반복해서 접근하면 캐시에 머무르며 더 느린 메모리로 나가는 비용을 결코 치르지 않습니다. 캐시는 할당이 이루어지는 풀이 아닙니다. 애플리케이션에게는 투명하며 – 단지 나머지 RAM이 실제로는 원래의 지연 시간이 시사하는 것보다 더 빠르게 느껴지게 할 뿐입니다. 단, 작업 집합(working set)이 캐시에 더 이상 들어가지 않게 되는 지점까지만 그렇습니다.

  • 긴밀하게 결합된 프로세서 메모리(tightly-coupled memory) – 중간에 버스 없이 CPU에 직접 연결된 작은 RAM 블록입니다. 단일 사이클 접근으로, 미스가 없고 대기도 없습니다. 진정으로 가능한 한 가장 빠른 메모리가 필요한 할당 – 모든 지연 시간의 한 사이클까지 중요한 경우 – 이 이 풀에서 나옵니다.

  • 빠른 온칩(on-chip) 메모리 – 수백 킬로바이트에서 약 1메가바이트에 이르는 RAM으로, MCU 패키지에 내장되어 있습니다. 지연 시간이 낮고 대역폭이 높지만 크기에 제한이 있습니다. Python 객체 접근이 빠르게 유지되도록 MicroPython 힙이 여기에 자리하며, CPU가 자주 건드리는 더 작은 작업 버퍼들이 이 풀을 공유합니다.

  • 더 느린 대용량 메모리 – MCU를 외부 메모리 다이와 짝지은 보드에서는, 외부 버스를 통해 접근하는 수십 메가바이트의 오프칩(off-chip) 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 객체 – 변수, 리스트, 딕셔너리, 클래스 인스턴스, snapshot() 호출이 반환하는 Image 래퍼, 그리고 애플리케이션이 생성하는 모든 문자열과 튜플 – 은 카메라의 메모리 풀과는 별개MicroPython 가비지 컬렉션 힙에 존재합니다. 가비지 컬렉션(GC) 힙은 MicroPython이 스스로 관리하는 메모리 영역으로, Python 코드는 객체가 생성될 때마다 암묵적으로 여기에서 할당하고, MicroPython은 주기적으로 힙을 스캔하여 애플리케이션이 더 이상 참조하지 않는 객체가 차지하던 공간을 회수하므로, 애플리케이션은 손수 무언가를 해제할 필요가 결코 없습니다.

GC 힙을 위해 부팅 시점에 전용 영역이 따로 마련되며, Python 접근이 빠르게 유지되도록 보통 빠른 온칩 메모리에 배치되고, 큰 데이터 구조를 위한 여유 공간이 더 필요한 보드에서는 더 큰 외부 블록으로의 선택적 오버플로가 허용됩니다.

snapshot() 이 반환하는 Image 는 GC 힙에 있는 작은 래퍼 객체이며, 실제 픽셀 데이터는 카메라의 풀 중 하나에 있는 프레임 버퍼에 존재합니다. 둘은 결코 같은 메모리를 두고 경쟁하지 않습니다.

4.19.6. 종합하기

각 종류의 할당을 알맞은 풀로 – 큰 버퍼는 들어갈 수 있는 더 큰 풀로, 지연 시간에 민감한 데이터는 더 빠른 풀로, Python 힙은 자체 영역으로, 프리뷰는 예약된 슬롯으로 – 보내는 것이 바로, 빠른 메모리가 총 몇 메가바이트밖에 없는 부품에서 전체 해상도 캡처 파이프라인, 프리뷰 채널, 그리고 간단치 않은 Python 스크립트를 나란히 실행할 수 있게 만드는 것입니다.