Internalizacija nizova u MicroPythonu

MicroPython koristi string interning kako bi uĹĄtedio i RAM i ROM. Time se izbjegava pohranjivanje dvostrukih kopija istog niza. Prvenstveno se to odnosi na identifikatore u vaĹĄem kodu, jer se neĹĄto poput naziva funkcije ili varijable vrlo vjerojatno pojavljuje na viĹĄe mjesta u kodu. U MicroPythonu se internalizirani niz naziva QSTR (uniQue STRing).

QSTR vrijednost (tipa qstr) je indeks u povezanoj listi QSTR spremnika (pools). QSTR-ovi pohranjuju svoju duljinu i hash svog sadrĹžaja za brzu usporedbu tijekom procesa uklanjanja duplikata. Sve operacije bytecodea koje rade s nizovima koriste QSTR argument.

Generiranje QSTR-ova pri prevoÄenju

U MicroPython C kodu, svi nizovi koji bi trebali biti internalizirani u konaÄnom ugraÄenom programu (firmware) zapisuju se kao MP_QSTR_Foo. Pri prevoÄenju to Äe se procijeniti na qstr vrijednost koja pokazuje na indeks "Foo" u QSTR spremniku.

ViĹĄekorakovni proces u Makefile omoguÄuje da ovo funkcionira. Ukratko, taj proces ima tri dijela:

  1. PronaÄi sve MP_QSTR_Foo tokene u kodu.

  2. Generirati statiÄki QSTR spremnik koji sadrĹži sve podatke nizova (ukljuÄujuÄi duljine i hasheve).

  3. Zamijeniti sve MP_QSTR_Foo (putem pretprocesora) njihovim odgovarajuÄim indeksom.

MP_QSTR_Foo tokeni traĹže se u dva izvora:

  1. Sve datoteke navedene u $(SRC_QSTR). To je sav C kod (tj. py, extmod, ports/stm32), ali ne ukljuÄuje kod treÄih strana poput lib.

  2. Dodatni $(QSTR_GLOBAL_DEPENDENCIES) (koji ukljuÄuje mpconfig*.h).

Napomena: frozen_mpy.c (generiran pomoÄu mpy-tool.py) ima vlastito generiranje QSTR-ova i vlastiti spremnik.

Neki dodatni nizovi koji se ne mogu izraziti sintaksom MP_QSTR_Foo (npr. sadrĹže nealfanumeriÄke znakove) eksplicitno se navode u qstrdefs.h i qstrdefsport.h putem varijable $(QSTR_DEFS).

Obrada se odvija u sljedeÄim fazama:

  1. qstr.i.last je rezultat spajanja svake pojedine ulazne datoteke koja je proĹĄla kroz C pretprocesor. To znaÄi da Äe se sav uvjetno onemoguÄen kod ukloniti, a makroi proĹĄiriti. To znaÄi da u spremnik ne dodajemo nizove koji se neÄe koristiti u konaÄnom ugraÄenom programu (firmware). BuduÄi da u ovoj fazi (zahvaljujuÄi makrou NO_QSTR koji dodaje QSTR_GEN_CFLAGS) nema definicije za MP_QSTR_Foo, on prolazi kroz ovu fazu nepromijenjen. Ova datoteka takoÄer ukljuÄuje komentare iz pretprocesora koji sadrĹže informacije o broju retka. Imajte na umu da ovaj korak koristi samo datoteke koje su se promijenile, ĹĄto znaÄi da Äe qstr.i.last sadrĹžavati samo podatke iz datoteka koje su se promijenile od posljednjeg prevoÄenja.

  2. qstr.split je prazna datoteka stvorena nakon pokretanja makeqstrdefs.py split na qstr.i.last. Koristi se samo kao ovisnost koja oznaÄava da je korak pokrenut. Ova skripta ispisuje jednu datoteku po ulaznoj C datoteci, genhdr/qstr/...file.c.qstr, koja sadrĹži samo podudarne QSTR-ove. Svaki QSTR ispisuje se kao Q(Foo). Ovaj korak je nuĹžan za kombiniranje postojeÄih datoteka s novim podacima generiranima iz inkrementalnog aĹžuriranja u qstr.i.last.

  3. qstrdefs.collected.h je rezultat spajanja genhdr/qstr/* pomoÄu makeqstrdefs.py cat. Ovo je sada potpuni skup MP_QSTR_Foo pronaÄenih u kodu, sada formatiran kao Q(Foo), jedan po retku, s duplikatima. Ova datoteka aĹžurira se samo ako se skup qstr-ova promijenio. Hash QSTR podataka zapisuje se u drugu datoteku (qstrdefs.collected.h.hash) koja omoguÄuje praÄenje promjena kroz buildove.

  4. Generirati enumeraciju, Äiji svaki unos preslikava MP_QSTR_Foo na njegov odgovarajuÄi indeks. Spaja qstrdefs.collected.h s qstrdefs*.h, zatim transformira svaki redak iz Q(Foo) u "Q(Foo)" kako bi proĹĄli kroz pretprocesor nepromijenjeni. Zatim se pretprocesor koristi za obradu bilo kakvog uvjetnog prevoÄenja u qstrdefs*.h. Zatim se transformacija poniĹĄtava natrag u Q(Foo) i sprema kao qstrdefs.preprocessed.h.

  5. qstrdefs.generated.h je rezultat makeqstrdata.py. Za svaki Q(Foo) u qstrdefs.preprocessed.h (uz neke dodatne Ävrstodefinirane), ispisuje QDEF(MP_QSTR_Foo, (const byte*)"hash" "Foo").

Zatim se u glavnom prevoÄenju dvije stvari dogaÄaju s qstrdefs.generated.h:

  1. U qstr.h, svaki QDEF postaje unos u enumu, ĹĄto Äini MP_QSTR_Foo dostupnim kodu i jednakim indeksu tog niza u QSTR tablici.

  2. U qstr.c, stvarna tablica QSTR podataka generira se kao elementi mp_qstr_const_pool->qstrs.

Generiranje QSTR-ova pri izvoÄenju

Dodatni QSTR spremnici mogu se stvoriti pri izvoÄenju tako da im se mogu dodavati nizovi. Na primjer, kod:

foo[x] = 3

Bit Äe potrebno stvoriti QSTR za vrijednost x kako bi je mogao koristiti „load attr” bytecode.

TakoÄer, pri prevoÄenju Python koda, za identifikatore i literale potrebno je stvoriti QSTR-ove. Napomena: samo literali kraÄi od 10 znakova postaju QSTR-ovi. To je zato ĹĄto obiÄan niz na gomili (heap) uvijek zauzima najmanje 16 bajtova (jedan GC blok), dok QSTR-ovi omoguÄuju da se uÄinkovitije zapakiraju u spremnik.

QSTR spremnici (i osnovni „chunkovi” koji pohranjuju podatke nizova) alociraju se na zahtjev na gomili (heap) s minimalnom veliÄinom.