Internace řetězců v MicroPythonu¶
MicroPython používá string interning k úspoře RAM i ROM. Tím se vyhne ukládání duplicitních kopií stejného řetězce. To se týká především identifikátorů ve vašem kódu, protože něco jako název funkce nebo proměnné se v kódu velmi pravděpodobně objeví na více místech. V MicroPythonu se internovaný řetězec nazývá QSTR (uniQue STRing, jedinečný řetězec).
Hodnota QSTR (typu qstr) je index do spojového seznamu zásobníků QSTR. QSTR ukládají svou délku a hash svého obsahu pro rychlé porovnání během procesu odstraňování duplicit. Všechny bytecodové operace, které pracují s řetězci, používají argument typu QSTR.
Generování QSTR při kompilaci¶
V C kódu MicroPythonu se jakékoli řetězce, které mají být internovány ve výsledném firmwaru, zapisují jako MP_QSTR_Foo. Při kompilaci se to vyhodnotí na hodnotu qstr, která ukazuje na index řetězce "Foo" v zásobníku QSTR.
Tuto funkcionalitu zajišťuje vícekrokový proces v souboru Makefile. Tento proces se ve zkratce skládá ze tří částí:
Najít v kódu všechny tokeny
MP_QSTR_Foo.Vygenerovat statický zásobník QSTR obsahující veškerá řetězcová data (včetně délek a hashů).
Nahradit všechny
MP_QSTR_Foo(prostřednictvím preprocesoru) jejich odpovídajícími indexy.
Tokeny MP_QSTR_Foo se vyhledávají ve dvou zdrojích:
Ve všech souborech odkazovaných v
$(SRC_QSTR). To je veškerý C kód (tj.py,extmod,ports/stm32), avšak nezahrnuje kód třetích stran, jako jelib.V dodatečných
$(QSTR_GLOBAL_DEPENDENCIES)(které zahrnujímpconfig*.h).
Poznámka: frozen_mpy.c (generovaný nástrojem mpy-tool.py) má vlastní generování QSTR a vlastní zásobník.
Některé dodatečné řetězce, které nelze vyjádřit pomocí syntaxe MP_QSTR_Foo (např. obsahují nealfanumerické znaky), jsou explicitně uvedeny v souborech qstrdefs.h a qstrdefsport.h prostřednictvím proměnné $(QSTR_DEFS).
Zpracování probíhá v následujících fázích:
qstr.i.lastje výsledek spojení všech jednotlivých vstupních souborů, které prošly C preprocesorem. To znamená, že jakýkoli podmíněně vypnutý kód bude odstraněn a makra budou rozvinuta. Díky tomu nepřidáváme do zásobníku řetězce, které ve výsledném firmwaru nebudou použity. Protože v této fázi (díky makruNO_QSTRpřidanému přesQSTR_GEN_CFLAGS) neexistuje žádná definice proMP_QSTR_Foo, projde tato fáze beze změny. Tento soubor také obsahuje komentáře z preprocesoru, které zahrnují informace o číslech řádků. Všimněte si, že tento krok používá pouze soubory, které se změnily, což znamená, žeqstr.i.lastbude obsahovat pouze data ze souborů, které se od poslední kompilace změnily.qstr.splitje prázdný soubor vytvořený po spuštěnímakeqstrdefs.py splitna qstr.i.last. Slouží pouze jako závislost indikující, že daný krok proběhl. Tento skript vytváří jeden soubor pro každý vstupní C soubor,genhdr/qstr/...file.c.qstr, který obsahuje pouze odpovídající QSTR. Každý QSTR je vypsán jakoQ(Foo). Tento krok je nezbytný pro spojení existujících souborů s novými daty vygenerovanými při inkrementální aktualizaci vqstr.i.last.qstrdefs.collected.hje výsledek spojenígenhdr/qstr/*pomocímakeqstrdefs.py cat. Jedná se nyní o úplnou množinuMP_QSTR_Foonalezených v kódu, formátovaných jakoQ(Foo), jeden na řádek, s duplikáty. Tento soubor se aktualizuje pouze tehdy, pokud se změnila množina qstr. Hash dat QSTR se zapisuje do jiného souboru (qstrdefs.collected.h.hash), což umožňuje sledovat změny mezi sestaveními.Vygeneruje výčet (enum), v němž každá položka mapuje
MP_QSTR_Foona jeho odpovídající index. Spojíqstrdefs.collected.hsqstrdefs*.ha poté transformuje každý řádek zQ(Foo)na"Q(Foo)", aby prošly preprocesorem beze změny. Poté je preprocesor použit k vyřešení jakékoli podmíněné kompilace vqstrdefs*.h. Poté je transformace vrácena zpět naQ(Foo)a uložena jakoqstrdefs.preprocessed.h.qstrdefs.generated.hje výstupmakeqstrdata.py. Pro každéQ(Foo)v qstrdefs.preprocessed.h (plus některé další pevně zakódované) vypíšeQDEF(MP_QSTR_Foo, (const byte*)"hash" "Foo").
Poté se při hlavní kompilaci se souborem qstrdefs.generated.h stanou dvě věci:
V qstr.h se každý QDEF stane položkou výčtu (enum), což zpřístupní
MP_QSTR_Fookódu a učiní jej rovným indexu daného řetězce v tabulce QSTR.V qstr.c je vlastní datová tabulka QSTR generována jako prvky
mp_qstr_const_pool->qstrs.
Generování QSTR za běhu¶
Za běhu lze vytvořit další zásobníky QSTR, aby do nich bylo možné přidávat řetězce. Například kód:
foo[x] = 3
Bude potřeba vytvořit QSTR pro hodnotu x, aby ji mohl použít bytecode „load attr“.
Také při kompilaci Python kódu je potřeba vytvořit QSTR pro identifikátory a literály. Poznámka: pouze literály kratší než 10 znaků se stávají QSTR. Důvodem je, že běžný řetězec na haldě vždy zabírá minimálně 16 bajtů (jeden GC blok), zatímco QSTR umožňují jejich efektivnější uspořádání do zásobníku.
Zásobníky QSTR (a podkladové „chunky“, které ukládají řetězcová data) jsou alokovány na haldě na vyžádání s minimální velikostí.