4.19. メモリプール¶
フレームバッファプールにフル解像度のフレームを3枚保持し、その横で別個のプレビューバッファを動かしつつ、さらにPythonスクリプトとそのオブジェクトを収める余地まで持つカメラは、MCU上の単一のRAMブロックでは到底まかなえない量のメモリをやりくりしています。MicroPythonは、MCUが提供するいくつかの異なる種類のメモリにそれらを分散させ、各種類の割り当てを実際に必要とするメモリの種類へ振り分けることで、すべてを収めています。
4.19.1. メモリの種類¶
現代のOpenMV Cam MCUは、4種類の異なるメモリを公開しています。最初のものはアプリケーションからは見えません。残りの3つは、割り当てが行われるプールです。
CPUのデータキャッシュ -- CPUとRAMのその他の部分との間に位置する、小さくて非常に高速なメモリ領域です。CPUがメインメモリから値を読み書きすると、キャッシュは自動的にそのコピーを保持するため、同じデータへの繰り返しのアクセスはキャッシュ内に留まり、より低速なメモリへ出向くコストを一切払いません。キャッシュは割り当てが行われるプールではありません。アプリケーションに対して透過的であり -- 単に、実際にはRAMのその他の部分を生のレイテンシが示すよりも高速に感じさせるだけです。ただし、ワーキングセットがそこに収まらなくなる時点までに限られます。
密結合プロセッサメモリ -- 間にバスを介さず、CPUに直接配線された小さなRAMブロックです。シングルサイクルアクセスで、決してミスせず、決して待たされません。本当に可能な限り最速のメモリを必要とする割り当て -- レイテンシの1サイクルが問題になるもの -- は、このプールから取られます。
高速なオンチップメモリ -- 数百キロバイトから約1メガバイトまでのRAMで、MCUパッケージに内蔵されています。低レイテンシ、高帯域幅ですが、サイズには制限があります。Pythonオブジェクトへのアクセスを高速に保つため、MicroPythonヒープはここに置かれます。CPUが頻繁に触れる小さめのワーキングバッファもこのプールを共有します。
低速な大容量メモリ -- MCUに外付けメモリダイを組み合わせたボードでは、外部バス経由でアクセスする数十メガバイトのオフチップRAMです。はるかに大きいものの、各アクセスにはオンチップメモリより時間がかかります。データキャッシュは、保持できるワーキングセットについてはそのコストの多くを隠しますが、キャッシュに収まらないほど大きなデータを走査する操作ではその差が表面化します。大きくなければならず、かつCPUが低速でも許容できる割り当て -- とりわけ重要なものとしてフレームバッファプール -- に使われます。
このファミリーのボードはスペクトラムのどこかに位置します。オンチップRAMのみを持つものもあれば、オンチップRAMにはるかに大きな外部ブロックを組み合わせたものもあります。割り当て可能な3種類のそれぞれはメモリプール -- 割り当てが取り出されるひとまとまり -- として扱われ、各リクエストが実際に必要とするメモリの種類を要求できるようラベル付けされています。
4.19.2. プライマリフレームバッファ¶
snapshot() を支えるフレームバッファは、高速メモリを要求しません。十分なメモリを要求します -- それ以上のものは要求しません。これにより、最も大きいプールがどれであろうとそこに配置されるため、オンチップメモリと外部メモリの両方を持つボードでは、フレームバッファは外部ブロックに収まります。
フル解像度・トリプルバッファのフレームバッファは、ほとんどの部品では高速なオンチッププールに収めるには大きすぎます。それを少しでも保持できるのは、より大きいプールだけです。アプリケーションが画像を処理する際、CPUのデータキャッシュがアクセスごとのコストの多くを隠し、フレームバッファをセンサーから埋めるDMAエンジンはいずれにせよセンサーのデータレートに追従します。
フレームバッファが取るサイズは、現在の pixformat()、framesize()、および framebuffers() の枚数から決定されます。これらのいずれかが変わるたびに、サイズは増減します。
4.19.3. セカンダリセンサーフレームバッファ¶
2つ目の CSI インスタンスは、プライマリが使うのと同じプールから割り当てられた、独自のフレームバッファを得ます。プールは共有されますが、バッファは独立しています。セカンダリのフットプリントは通常プライマリのものよりはるかに小さくなります。これはセカンダリセンサーが低い解像度で動作するためで、2つ目のフレームバッファが取る追加のメモリはプライマリのもののわずかな割合にすぎません。
4.19.4. ストリームフレームバッファ¶
画像プレビュー バッファは例外です。これは実行時にいずれのプールからも割り当てられません。既知のアドレスと既知のサイズを持つ、ビルド時に予約された固定領域です。これにより、プレビューパスが他のすべての割り当ての邪魔にならないようにします -- この領域はブート時から存在し、決して移動しません。
4.19.5. MicroPythonヒープ¶
Pythonオブジェクト -- 変数、リスト、辞書、クラスインスタンス、snapshot() の呼び出しが返す Image ラッパー、アプリケーションが作成するすべての文字列とタプル -- は、MicroPythonのガベージコレクションヒープ上に存在します。これはカメラのメモリプールとは別個のものです。ガベージコレクション(GC)ヒープは、MicroPython自身が管理するメモリ領域です。Pythonコードはオブジェクトが作成されるたびに暗黙的にそこから割り当て、MicroPythonは定期的にヒープを走査して、アプリケーションがもはや参照していないオブジェクトが占めていた領域を回収します。そのため、アプリケーションは何かを手作業で解放する必要が一切ありません。
GCヒープ用には専用の領域がブート時に確保され、Pythonアクセスを高速に保つため通常は高速なオンチップメモリに配置されます。大きなデータ構造のためにより多くの余裕を必要とするボードでは、より大きな外部ブロックへのオプションのオーバーフローが用意されます。
snapshot() が返す Image は、GCヒープ上の小さなラッパーオブジェクトです。その下層のピクセルデータは、カメラのプールのいずれかにあるフレームバッファ内に存在します。この2つが同じメモリを取り合うことは決してありません。
4.19.6. まとめ¶
各種類の割り当てを適切なプールへ誘導すること -- 大きなバッファは収まるより大きいプールへ、レイテンシに敏感なデータはより高速なプールへ、Pythonヒープは独自の領域へ、プレビューは予約済みのスロットへ -- こそが、高速メモリの総量が数メガバイトしかない部品の上で、フル解像度のキャプチャパイプライン、プレビューチャンネル、そして単純でないPythonスクリプトを互いに並行して動かすことを可能にしているのです。