メモリー管理
C/C++ などのプログラミング言語と違い、MicroPython は自動メモリー管理をサポートすることで、すべての Python オブジェクトを保持する「 Python ヒープ 」のメモリー管理の詳細を開発者から隠蔽しています。MicroPython は、自動メモリ管理にガベージコレクション(GC: Garbage Collection)を使います。ガベージコレクタは、使わなくなったメモリを解放する役割を担います。
具体的に、MicroPython は マークアンドスイープ方式 のガベージコレクションアルゴリズムを採用しています。このアルゴリズムは、ヒープをスキャンしてすべての生存オブジェクトにマークを付けるマークフェーズと、ヒープをスキャンしてマークされていないすべてのオブジェクトを解放するスイープフェーズで構成されています。
MicroPython のガベージコレクタはデフォルトでは自動ですが、 gc 組込みモジュールを使って手動で制御することも可能です。
C コードからの MicroPython メモリ
「Python ヒープ」からメモリを割り当てる C コード(m_malloc(), m_malloc0(), m_free() などの関数)を書く際には、ガベージコレクタの仕組みを理解しておく必要があります。
ガベージコレクタのマークフェーズでは、以下のルートから始まるヒープメモリへの有効なポインタをスキャンします。
メインの Python ランタイム(または REPL)のスタック。
ネイティブのオペレーティングシステムのスレッドまたはタスク上に Python スレッドを実装するポートの場合、各「Python スレッド」のスタック。
マクロ
MP_REGISTER_ROOT_POINTERを使って C コードで定義された「ルートポインタ」。これは Python ヒープへの静的スコープポインタを持つための推奨される方法です。m_tracked_calloc(),m_tracked_realloc,m_tracked_free()関数を使って行われた追跡可能なメモリ割り当て。これらの特殊関数を使用すると、ガベージコレクタによって常に有効とみなされるメモリブロックを割り当てることができます。C 言語のメモリ割り当てと同様に、このメモリはm_tracked_free()を呼び出すか、ソフトリセットを実行することによってのみ解放されます。追跡可能なメモリ割り当てごとに、わずかなメモリ使用量と実行時オーバーヘッドが発生します。この機能は、すべてのポートでデフォルトで有効になっているわけではありません。
ガベージコレクタは、ルートポインタが指すすべてのメモリを再帰的にスキャンしてマークし、すべてのアドレスが使い果たされるまで続けます。これにより、MicroPython ランタイムによってまだ使われているすべての Python オブジェクトを見つけられます。
ただし、以下のメモリはガベージコレクタによってスキャンされず、早期に解放される可能性があります。
ヒープメモリへのポインタを含む静的またはグローバルなC変数。
割り当て済みバッファの「先頭」(つまり
m_malloc()によって返される正確なアドレス)ではなく、割り当て済みバッファ内のアドレス(たとえば、ネストされた構造体へのポインタ)を指すポインタ。パフォーマンス上の理由から、これらの場合、ガベージコレクタは囲んでいるバッファをマークしません。Pythonコードを実行していない、または「Pythonスレッド」として手動で登録されていないスレッドまたは RTOS タスクのスタック(ネイティブスレッドまたはタスクをサポートするポートの場合)。
このようなシナリオで解放後の使用を回避する方法:
トラッキングする割り当て API である
m_tracked_calloc(),m_tracked_realloc(),m_tracked_free()を使う。静的変数にポインタを格納する代わりに、ルートポインタを登録する(上記参照)。
コードを再構築する。たとえば、Python コードがすべての関連ポインタを保持するシングルトン Python オブジェクト(C で実装)を初期化する API を用意するなどして、静的変数にポインタを格納する代わりに、コードを再構築する。
注釈
ソフトリセット は常に Python ヒープをクリアし、すべてのメモリを解放します。ソフトリセット後はヒープへのポインタを保持しないことが重要です。解放されたメモリへのダングリングポインタになるからです。
一部のポートは「C ヒープ」もサポートしています(C の動的メモリ割当て を参照)。その場合、標準の C 関数 malloc などを呼び出すことで、ソフトリセット後も有効なメモリを割り当てられます。
オブジェクトモデル
すべての MicroPython オブジェクトは mp_obj_t データ型で参照されます。これは通常、ワードサイズ(つまり、ターゲットアーキテクチャ上のポインタと同じサイズ)で、32ビット(STM32、nRF、ESP32、Unix x86)または64ビット(Unix x64)であることが多いです。また、特定のオブジェクト表現では、ワードサイズより大きくなることもあります。たとえば OBJ_REPR_D は 32 ビットアーキテクチャ上であっても 64 ビットサイズの mp_obj_t を持ちます。
mp_obj_t は MicroPython オブジェクトを表します。たとえば、整数、浮動小数点数、型、辞書、クラスのインスタンスなどです。 真偽値や小さな整数のようなオブジェクトは、その値が直接 mp_obj_t の値に格納されるので、追加のメモリは必要ありません。他のオブジェクトはその値がメモリの他の場所(たとえば、ガベージコレクション対象のヒープ上)に格納され、その mp_obj_t はそのメモリへのポインターを持っています。 mp_obj_t 内の一部は、それがどのような種類のオブジェクトであるかを示すタグになっています。
利用可能な表現の具体的な詳細については py/mpconfig.h を参照してください。
ポインタータグ
ポインターはワードアラインされているので、 mp_obj_t に格納する場合、このオブジェクトハンドルの下位ビットはゼロになります。たとえば、32 ビットアーキテクチャでは、下位 2 ビットは 0 になります。
********|********|********|******00
これらのビットは、タグを格納するために予約されています。このタグに付加情報を格納することにより、オブジェクトに新しいフィールドを用意して情報を格納するような非効率化を避けています。MicroPython において、タグは小さな整数、隔離化した(サイズの小さな)文字列、具象オブジェクトを扱っているかどうかを示し、それぞれに異なるセマンティクスが適用されます。
小さい整数の場合、マッピングは次のようになります:
********|********|********|*******1
上の表現で、アスタリスクは実際の整数値を表しています。隔離化した文字列または即値オブジェクト(True など)の場合、 `` mp_obj_t`` 値のレイアウトはそれぞれ次のようになります:
********|********|********|*****010
********|********|********|*****110
上記のいずれでもない具象オブジェクトは、次のような形式をとります:
********|********|********|******00
上記のアスタリスクは、メモリー上の具象オブジェクトのアドレスに相当します。
オブジェクトの割当て
小さな整数の値は mp_obj_t に直接格納され、ヒープなどではなく、埋め込みで割り当てられることになります。そのため、小さな整数の作成はヒープに影響を与えません。同様に、すでにテキストデータが別の場所に保存されている隔離化文字列や、 None, False, True などの即値も同様です。
それ以外の具象オブジェクトはすべてヒープ上に確保されます。そのオブジェクト構造は、オブジェクトの型を格納するフィールドがオブジェクトヘッダに予約されているようなものです。
+++++++++++
+ +
+ 型 + オブジェクトヘッダー
+ +
+++++++++++
+ + オブジェクトアイテム
+ +
+ +
+++++++++++
ヒープの割当ての最小単位はブロックであり、その大きさは4マシンワード(32ビットマシンでは16バイト、64ビットマシンでは32バイト)です。また、ヒープに割り当てられた別の構造体が、各ブロック内のオブジェクトの割り当てを追跡します。この構造体はビットマップと呼ばれています。
ビットマップは、ブロックが「空き」なのか「使用中」なのかを追跡し、ブロックごとに2ビットを使います。
マーク&スイープガベージコレクションは、ヒープ上に割り当てられたオブジェクトを管理し、また、まだ使用中のオブジェクトをマークするためにビットマップを利用します。これらの詳細の完全な実装は py/gc.c を参照してください。
割当て: ヒープレイアウト
ヒープはプール内のブロックから構成されるように配置されます。ブロックは異なるプロパティを持てます。
ATB(Allocation Table Byte): 設定されている場合、そのブロックは通常のブロックです。
FREE: フリーブロック
HEAD: ブロックのチェーンの先頭
TAIL: ブロックのチェーンの最後尾
MARK: マークされたヘッドブロック
FTB(Finaliser Table Byte): 設定されている場合,そのブロックはファイナライザを持ちます