MicroPython sztring-internálás

A MicroPython string interning (sztring-internálás) technikát használ a RAM és a ROM megtakarítása érdekében. Ezzel elkerülhető, hogy ugyanazon sztring duplikált példányait kelljen tárolni. Ez elsősorban a kódban szereplő azonosítókra vonatkozik, mivel egy függvény- vagy változónévhez hasonló dolog nagyon valószínűen több helyen is megjelenik a kódban. A MicroPythonban az internált sztringet QSTR-nek (uniQue STRing) nevezik.

Egy QSTR érték (qstr típussal) egy index a QSTR-poolok láncolt listájában. A QSTR-ek tárolják a hosszukat és a tartalmuk hash-ét a gyors összehasonlítás érdekében a deduplikációs folyamat során. Minden, sztringekkel dolgozó bájtkód-művelet QSTR argumentumot használ.

Fordítási idejű QSTR-generálás

A MicroPython C kódjában minden olyan sztring, amelyet a végleges firmware-ben internálni kell, MP_QSTR_Foo formában van leírva. Fordítási időben ez egy qstr értékké értékelődik ki, amely a "Foo" indexére mutat a QSTR-poolban.

Egy többlépcsős folyamat a Makefile-ban gondoskodik erről. Összefoglalva, ez a folyamat három részből áll:

  1. Az összes MP_QSTR_Foo token megkeresése a kódban.

  2. Egy statikus QSTR-pool generálása, amely tartalmazza az összes sztringadatot (beleértve a hosszokat és hash-eket).

  3. Az összes MP_QSTR_Foo lecserélése (az előfeldolgozón keresztül) a hozzájuk tartozó indexre.

Az MP_QSTR_Foo tokeneket két forrásban keresi:

  1. Az összes, a $(SRC_QSTR)-ben hivatkozott fájl. Ez az összes C kód (azaz py, extmod, ports/stm32), de nem tartalmazza a harmadik féltől származó kódot, mint például a lib.

  2. További $(QSTR_GLOBAL_DEPENDENCIES) (amely tartalmazza az mpconfig*.h-t).

Megjegyzés: a frozen_mpy.c (amelyet az mpy-tool.py generál) saját QSTR-generálással és pool-lal rendelkezik.

Néhány további sztring, amely nem fejezhető ki az MP_QSTR_Foo szintaxissal (pl. nem alfanumerikus karaktereket tartalmaz), explicit módon a qstrdefs.h és qstrdefsport.h fájlokban van megadva a $(QSTR_DEFS) változón keresztül.

A feldolgozás a következő szakaszokban történik:

  1. A qstr.i.last az összes bemeneti fájl C előfeldolgozón való átfuttatásának összefűzése. Ez azt jelenti, hogy minden feltételesen letiltott kód eltávolításra kerül, és a makrók kifejtődnek. Ez azt jelenti, hogy nem adunk hozzá a poolhoz olyan sztringeket, amelyek nem lesznek használva a végleges firmware-ben. Mivel ebben a szakaszban (a QSTR_GEN_CFLAGS által hozzáadott NO_QSTR makrónak köszönhetően) nincs definíció az MP_QSTR_Foo-ra, az érintetlenül halad át ezen a szakaszon. Ez a fájl tartalmazza az előfeldolgozótól származó megjegyzéseket is, amelyek sorszám-információkat tartalmaznak. Vegye figyelembe, hogy ez a lépés csak a megváltozott fájlokat használja, ami azt jelenti, hogy a qstr.i.last csak az utolsó fordítás óta megváltozott fájlokból tartalmaz adatokat.

  2. A qstr.split egy üres fájl, amelyet a makeqstrdefs.py split qstr.i.last-on való futtatása után hoz létre. Csupán függőségként szolgál annak jelzésére, hogy a lépés lefutott. Ez a szkript bemeneti C fájlonként egy fájlt ad ki, genhdr/qstr/...file.c.qstr néven, amely csak az illeszkedő QSTR-eket tartalmazza. Minden QSTR Q(Foo) formában kerül kiírásra. Erre a lépésre a meglévő fájlok és a qstr.i.last-ben lévő inkrementális frissítésből generált új adatok kombinálásához van szükség.

  3. A qstrdefs.collected.h a genhdr/qstr/* fájlok makeqstrdefs.py cat-tel való összefűzésének kimenete. Ez most a kódban talált MP_QSTR_Foo-k teljes halmaza, immár Q(Foo) formátumban, soronként egy, duplikátumokkal. Ez a fájl csak akkor frissül, ha a qstr-ek halmaza megváltozott. A QSTR-adatok hash-e egy másik fájlba (qstrdefs.collected.h.hash) kerül kiírásra, amely lehetővé teszi a változások nyomon követését a buildek között.

  4. Egy felsorolás (enum) generálása, amelynek minden bejegyzése egy MP_QSTR_Foo-t leképez a hozzá tartozó indexre. Összefűzi a qstrdefs.collected.h-t a qstrdefs*.h-val, majd minden sort Q(Foo)-ból "Q(Foo)"-ra alakít, hogy változatlanul áthaladjanak az előfeldolgozón. Ezután az előfeldolgozóval kezeli a qstrdefs*.h-ban lévő feltételes fordítást. Majd az átalakítást visszafordítja Q(Foo)-ra, és qstrdefs.preprocessed.h néven menti.

  5. A qstrdefs.generated.h a makeqstrdata.py kimenete. A qstrdefs.preprocessed.h-ban lévő minden Q(Foo)-ra (plusz néhány extra, kódban rögzítettre) kiad egy QDEF(MP_QSTR_Foo, (const byte*)"hash" "Foo")-t.

Ezután a fő fordítás során két dolog történik a qstrdefs.generated.h-val:

  1. A qstr.h-ban minden QDEF egy enum-bejegyzéssé válik, ami az MP_QSTR_Foo-t elérhetővé teszi a kód számára, és egyenlővé teszi az adott sztring QSTR-táblában lévő indexével.

  2. A qstr.c-ben a tényleges QSTR-adattábla az mp_qstr_const_pool->qstrs elemeiként generálódik.

Futásidejű QSTR-generálás

Futásidőben további QSTR-poolok hozhatók létre, hogy sztringeket lehessen hozzájuk adni. Például a következő kód:

foo[x] = 3

QSTR-t kell létrehoznia az x értékéhez, hogy a „load attr” bájtkód használni tudja.

Továbbá Python kód fordításakor az azonosítókhoz és literálokhoz QSTR-eket kell létrehozni. Megjegyzés: csak a 10 karakternél rövidebb literálok válnak QSTR-ekké. Ennek oka, hogy egy szokásos sztring a heap-en mindig legalább 16 bájtot foglal el (egy GC-blokk), míg a QSTR-ek lehetővé teszik a hatékonyabb betömörítésüket a poolba.

A QSTR-poolok (és az alapjául szolgáló „chunk”-ok, amelyek a sztringadatokat tárolják) igény szerint, minimális mérettel kerülnek lefoglalásra a heap-en.