4.19. 記憶體池¶
一台在影格緩衝區池中保存三張全解析度影格、同時並行運行一個獨立預覽緩衝區、並且還有空間容納 Python 指令碼及其物件的相機,所調度的記憶體遠超過 MCU 上單一區塊 RAM 所能提供的量。MicroPython 之所以能把這一切全部塞進去,是因為它將這些資料分散到 MCU 所提供的數種不同類型的記憶體之中,並把每一種配置請求導向它真正需要的那種記憶體。
4.19.1. 記憶體的種類¶
現代的 OpenMV Cam MCU 提供四種不同類型的記憶體。第一種對應用程式而言是看不見的;其餘三種則是配置請求可以從中取得記憶體的池。
CPU 的資料快取——位於 CPU 與其餘 RAM 之間的一小塊極快速記憶體區域。當 CPU 從主記憶體讀取或寫入某個值時,快取會自動保留一份副本,因此對同一筆資料的重複存取都停留在快取中,永遠不必付出前往較慢記憶體的代價。快取並非配置請求可取得記憶體的池。它對應用程式是透明的——它只是讓其餘的 RAM 在實際使用上感覺比其原始延遲所暗示的更快,直到工作集大到無法再容納其中為止。
緊密耦合處理器記憶體——一小塊直接接線到 CPU、中間沒有任何匯流排的 RAM。單一週期存取,永遠不會未命中,永遠不必等待。那些真正需要最快速記憶體的配置——也就是每一個週期的延遲都至關重要的場合——都從這個池中取得。
快速晶片內記憶體——數百 KB 到約 1 MB 的 RAM,內建於 MCU 封裝之中。低延遲、高頻寬,但容量有限。MicroPython 的堆積(heap)就駐留在這裡,讓 Python 物件的存取保持快速;CPU 經常接觸的較小工作緩衝區也共享這個池。
較慢的大容量記憶體——在那些把 MCU 與外部記憶體晶粒配對的開發板上,這是透過外部匯流排存取、達數十 MB 的晶片外 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 堆積送往它自己的區域、預覽送往它保留的位置——正是這件事,讓我們得以在總共只有區區數 MB 快速記憶體的零件上,同時運行一條全解析度的擷取管線、一條預覽通道,以及一支不算簡單的 Python 指令碼。