Interning string MicroPython¶
MicroPython menggunakan string interning untuk menghemat RAM maupun ROM. Ini menghindari penyimpanan salinan duplikat dari string yang sama. Terutama, ini berlaku untuk identifier dalam kode Anda, karena sesuatu seperti nama fungsi atau variabel sangat mungkin muncul di beberapa tempat dalam kode. Dalam MicroPython, string yang di-intern disebut QSTR (uniQue STRing).
Nilai QSTR (dengan tipe qstr) adalah indeks ke dalam linked list dari pool QSTR. QSTR menyimpan panjang dan hash dari kontennya untuk perbandingan cepat selama proses de-duplikasi. Semua operasi bytecode yang bekerja dengan string menggunakan argumen QSTR.
Pembuatan QSTR saat kompilasi¶
Dalam kode C MicroPython, string apa pun yang harus di-intern dalam firmware akhir ditulis sebagai MP_QSTR_Foo. Pada saat kompilasi, ini akan dievaluasi menjadi nilai qstr yang menunjuk ke indeks "Foo" dalam pool QSTR.
Proses multi-langkah dalam Makefile membuat ini bekerja. Secara ringkas, proses ini memiliki tiga bagian:
Temukan semua token
MP_QSTR_Foodalam kode.Buat pool QSTR statis yang berisi semua data string (termasuk panjang dan hash).
Ganti semua
MP_QSTR_Foo(melalui preprocessor) dengan indeks yang sesuai.
Token MP_QSTR_Foo dicari di dua sumber:
Semua file yang direferensikan dalam
$(SRC_QSTR). Ini adalah semua kode C (yaitupy,extmod,ports/stm32) tetapi tidak termasuk kode pihak ketiga sepertilib.$(QSTR_GLOBAL_DEPENDENCIES)tambahan (yang mencakupmpconfig*.h).
Catatan: frozen_mpy.c (dibuat oleh mpy-tool.py) memiliki pembuatan QSTR dan pool-nya sendiri.
Beberapa string tambahan yang tidak dapat diekspresikan menggunakan sintaks MP_QSTR_Foo (misalnya mengandung karakter non-alfanumerik) disediakan secara eksplisit dalam qstrdefs.h dan qstrdefsport.h melalui variabel $(QSTR_DEFS).
Pemrosesan terjadi dalam tahapan berikut:
qstr.i.lastadalah penggabungan dari memasukkan setiap file input tunggal melalui preprocessor C. Ini berarti kode yang dinonaktifkan secara kondisional akan dihapus, dan makro diperluas. Ini berarti kita tidak menambahkan string ke pool yang tidak akan digunakan dalam firmware akhir. Karena pada tahap ini (berkat makroNO_QSTRyang ditambahkan olehQSTR_GEN_CFLAGS) tidak ada definisi untukMP_QSTR_Foo, maka ia melewati tahap ini tanpa terpengaruh. File ini juga mencakup komentar dari preprocessor yang berisi informasi nomor baris. Perhatikan bahwa langkah ini hanya menggunakan file yang telah berubah, yang berartiqstr.i.lasthanya akan berisi data dari file yang telah berubah sejak kompilasi terakhir.qstr.splitadalah file kosong yang dibuat setelah menjalankanmakeqstrdefs.py splitpada qstr.i.last. File ini hanya digunakan sebagai dependensi untuk menunjukkan bahwa langkah tersebut telah berjalan. Skrip ini menghasilkan satu file per file C input,genhdr/qstr/...file.c.qstr, yang hanya berisi QSTR yang cocok. Setiap QSTR dicetak sebagaiQ(Foo). Langkah ini diperlukan untuk menggabungkan file yang ada dengan data baru yang dihasilkan dari pembaruan inkremental dalamqstr.i.last.qstrdefs.collected.hadalah output dari penggabungangenhdr/qstr/*menggunakanmakeqstrdefs.py cat. Ini sekarang merupakan kumpulan lengkapMP_QSTR_Fooyang ditemukan dalam kode, sekarang diformat sebagaiQ(Foo), satu per baris, dengan duplikat. File ini hanya diperbarui jika kumpulan qstr telah berubah. Hash dari data QSTR ditulis ke file lain (qstrdefs.collected.h.hash) yang memungkinkannya melacak perubahan antar build.Buat enumerasi, yang setiap entrinya memetakan
MP_QSTR_Fooke indeks yang sesuai. Enumerasi ini menggabungkanqstrdefs.collected.hdenganqstrdefs*.h, kemudian mengubah setiap baris dariQ(Foo)menjadi"Q(Foo)"agar melewati preprocessor tanpa perubahan. Kemudian preprocessor digunakan untuk menangani kompilasi kondisional apa pun dalamqstrdefs*.h. Kemudian transformasi dibatalkan kembali keQ(Foo), dan disimpan sebagaiqstrdefs.preprocessed.h.qstrdefs.generated.hadalah output darimakeqstrdata.py. Untuk setiapQ(Foo)dalam qstrdefs.preprocessed.h (ditambah beberapa yang di-hardcode), ia menghasilkanQDEF(MP_QSTR_Foo, (const byte*)"hash" "Foo").
Kemudian dalam kompilasi utama, dua hal terjadi dengan qstrdefs.generated.h:
Dalam qstr.h, setiap QDEF menjadi entri dalam enum, yang membuat
MP_QSTR_Footersedia untuk kode dan sama dengan indeks string tersebut dalam tabel QSTR.Dalam qstr.c, tabel data QSTR aktual dibuat sebagai elemen dari
mp_qstr_const_pool->qstrs.
Pembuatan QSTR saat runtime¶
Pool QSTR tambahan dapat dibuat saat runtime sehingga string dapat ditambahkan ke dalamnya. Sebagai contoh, kode berikut:
foo[x] = 3
Perlu membuat QSTR untuk nilai x agar dapat digunakan oleh bytecode "load attr".
Selain itu, saat mengompilasi kode Python, identifier dan literal perlu dibuat QSTRnya. Catatan: hanya literal yang lebih pendek dari 10 karakter yang menjadi QSTR. Ini karena string biasa di heap selalu menggunakan setidaknya 16 byte (satu blok GC), sedangkan QSTR memungkinkan mereka dikemas lebih efisien ke dalam pool.
Pool QSTR (dan "chunk" yang mendasarinya yang menyimpan data string) dialokasikan sesuai permintaan di heap dengan ukuran minimum.