MicroPython 文字列の隔離化¶
MicroPython は 文字列の隔離化 を使って RAM と ROM の両方に保存します。これにより、同じ文字列の重複コピーを保存する必要がなくなります。関数または変数名のようなものがコードの複数の場所に現れる可能性が非常に高いため、これは主にコードの識別子に適用されます。MicroPython では、インターンされた文字列は QSTR (uniQue STRing)と呼ばれます。
QSTR 値(qstr
型)は、QSTR プールのリンクリストへのインデックスです。QSTRは重複排除プロセス中の高速比較のために、長さとコンテンツのハッシュを保存します。文字列を操作するすべてのバイトコード操作は、QSTR 引数を使います。
コンパイル時の QSTR 生成¶
MicroPython の C ソースコードでは、ビルド後のファームウェアに隔離する必要のある文字列はすべて MP_QSTR_Foo
として書いてあります。これはコンパイル時に、QSTR プール内の "Foo"
を指すインデックスである qstr
値に評価されます。
Makefile
中の多段階の処理がこの機能を実現しています。この処理は大きく次の3つの段階があります:
ソースコード中のすべての
MP_QSTR_Foo
トークンを検出すべての文字列データ(長さとハッシュを含む)を持つ静的な QSTR プールを生成
MP_QSTR_Foo
すべてを(プリプロセッサを使って)対応するインデックスに置換
MP_QSTR_Foo
トークンは次の2つのソースコードから検索されます:
$(SRC_QSTR)
で参照されるすべてのソースファイル。これは、すべてのCコード(すなわちpy
,extmod
,ports/stm32
など)ですが、lib
のようなサードパーティのコードは含みません。$(QSTR_GLOBAL_DEPENDENCIES)
で追加されるもの(mpconfig*.h
を含む)。
注記: (mpy-tool.py によって生成される) frozen_mpy.c
には独自の QSTR 生成とプールがあります。
MP_QSTR_Foo
構文を使って表現できない追加の文字列(たとえば、英数字以外の文字を含んだもの)は、 $(QSTR_DEFS)
に指定する qstrdefs.h
と qstrdefsport.h
で明示的に与えます。
処理は次の段階を踏みます:
qstr.i.last
は、C プリプロセッサをとおした各入力ファイルを結合したものです。これは、条件付きで無効化されたコードが削除され、マクロが展開されていることを意味します。これは、ビルドしたファームウェアで使われていない文字列をプールに追加しないことを意味します。この段階では(QSTR_GEN_CFLAGS
によって追加されたNO_QSTR
マクロのおかげで)MP_QSTR_Foo
のための定義がないため、何の作用もなくこの段階を通過します。このファイルには、行番号情報を含むプリプロセッサからのコメントも含まれます。この段階では、変更されたファイルのみを使うことに注意してください。つまりqstr.i.last
には直前のコンパイル以降に変更されたファイルのデータのみが含まれます。qstr.split
は qstr.i.last に対してmakeqstrdefs.py split
を実行して作成される空のファイルです。このファイルは、この段階が実行されたことを示すための依存関係としてのみ使われます。このスクリプトは、元の入力 C ソースファイルごとに1つのファイルを出力します。出力ファイルはgenhdr/qstr/...file.c.qstr
のようなファイル名であり、ファイルの中身は抽出した QSTR のみとなります。各 QSTR はQ(Foo)
の形式で出力されます。この段階は、qstr.i.last
にある増分更新から生成された新しいデータを既存ファイルにマージするために必要です。qstrdefs.collected.h
は、makeqstrdefs.py cat
を使ってgenhdr/qstr/*
を全て結合した出力です。これは、ソースコード内に見つかったMP_QSTR_Foo
の完全なセットです。フォーマットはQ(Foo)
のまま、1行に1つずつ、重複ありとなっています。このファイルは、qstr のセットが変更された場合にのみ更新されます。QSTR データのハッシュは別のファイル(qstrdefs.collected.h.hash
)に書き込まれ、ビルド全体の変更を追跡できます。Generate an enumeration, each entry of which maps a
MP_QSTR_Foo
to it's corresponding index. It concatenatesqstrdefs.collected.h
withqstrdefs*.h
, then it transforms each line fromQ(Foo)
to"Q(Foo)"
so they pass through the preprocessor unchanged. Then the preprocessor is used to deal with any conditional compilation inqstrdefs*.h
. Then the transformation is undone back toQ(Foo)
, and saved asqstrdefs.preprocessed.h
.qstrdefs.generated.h
はmakeqstrdata.py
の出力です。qstrdefs.preprocessed.h
(と、追加でハードコーディングされたもの)中のQ(Foo)
それぞれについてQDEF(MP_QSTR_Foo, (const byte*)"hash" "Foo")
を出力します。
次に、メインのコンパイルで、 qstrdefs.generated.h
について次の2つのことが起きます:
qstr.h
では、各 QDEF は列挙型のエントリになり、ソースコード中でMP_QSTR_Foo
を使えるようになります。これは QSTR テーブル内の文字列のインデックスと同等です。qstr.c
では、実際の QSTR データテーブルがmp_qstr_const_pool->qstrs
の要素として生成されます。
実行時のQSTR生成¶
実行時にも追加の QSTR プールを作成して、文字列を追加できるようになっています。たとえば、次のようなコードがあるものとします:
foo[x] = 3
x
の値の QSTR を作成されれば、それを "load attr" バイトコードで使えるようになります。
また、Python コードをコンパイルするときは、識別子とリテラルの QSTR が作成される必要があります。注記: 10文字より短いリテラルのみが QSTR になります。これは、ヒープ上の通常の文字列が常に最小16バイト(1 GCブロック)を占有するのに対し、QSTR を使った場合はより効率的にプールにパックできるためです。
QSTRプール(および文字列データを格納する基盤となる「チャンク」)は、最小サイズでオンデマンドでヒープに割り当てられます。