MicroPython mikrokontrollereissa¶
MicroPython on suunniteltu toimimaan mikrokontrollereissa. Niissä on laitteistorajoituksia, jotka voivat olla vieraita ohjelmoijille, jotka ovat tottuneet tavanomaisiin tietokoneisiin. Erityisesti RAM-muistin ja haihtumattoman ”levytilan” (flash-muistin) määrä on rajallinen. Tämä opastus tarjoaa keinoja hyödyntää rajalliset resurssit mahdollisimman tehokkaasti. Koska MicroPython toimii erilaisiin arkkitehtuureihin perustuvissa kontrollereissa, esitetyt menetelmät ovat yleisluonteisia: joissakin tapauksissa on tarpeen hankkia yksityiskohtaista tietoa alustakohtaisesta dokumentaatiosta.
Flash-muisti¶
OpenMV Cam -kameroissa yksinkertainen tapa ratkaista rajallinen kapasiteetti on asentaa micro SD -kortti. Joissakin tapauksissa tämä on epäkäytännöllistä joko siksi, että laitteessa ei ole SD-korttipaikkaa, tai kustannus- tai virrankulutussyistä; tällöin on käytettävä piirin sisäistä flash-muistia. Laiteohjelmisto, mukaan lukien MicroPython-alijärjestelmä, on tallennettu sisäänrakennettuun flash-muistiin. Jäljelle jäävä kapasiteetti on käytettävissä. Flash-muistin fyysiseen arkkitehtuuriin liittyvistä syistä osa tästä kapasiteetista voi olla saavuttamattomissa tiedostojärjestelmänä. Tällaisissa tapauksissa tämä tila voidaan ottaa käyttöön sisällyttämällä käyttäjän moduulit laiteohjelmiston koontiversioon, joka sitten flashataan laitteeseen.
Tämän voi saavuttaa kahdella tavalla: jäädytetyillä moduuleilla ja jäädytetyllä tavukoodilla. Jäädytetyt moduulit tallentavat Python-lähdekoodin laiteohjelmiston yhteyteen. Jäädytetty tavukoodi käyttää ristikääntäjää muuntaakseen lähdekoodin tavukoodiksi, joka sitten tallennetaan laiteohjelmiston yhteyteen. Kummassakin tapauksessa moduulia voidaan käyttää import-lauseella:
import mymodule
Jäädytettyjen moduulien ja tavukoodin tuottamismenettely on alustakohtainen; laiteohjelmiston koontiohjeet löytyvät lähdepuun asianomaisen osan README-tiedostoista.
Yleisesti ottaen vaiheet ovat seuraavat:
Kloonaa MicroPython-tietovarasto.
Hanki (alustakohtainen) työkaluketju laiteohjelmiston koontia varten.
Koonna ristikääntäjä.
Sijoita jäädytettävät moduulit määriteltyyn hakemistoon (riippuen siitä, jäädytetäänkö moduuli lähdekoodina vai tavukoodina).
Koonna laiteohjelmisto. Kumman tahansa tyypin jäädytetyn koodin koontiin voidaan tarvita erityinen komento - katso alustan dokumentaatio.
Flashaa laiteohjelmisto laitteeseen.
RAM¶
RAM-muistin käyttöä vähennettäessä on otettava huomioon kaksi vaihetta: käännös ja suoritus. Muistinkulutuksen lisäksi on olemassa myös ilmiö, joka tunnetaan keon pirstoutumisena. Yleisesti ottaen on parasta minimoida olioiden toistuva luominen ja tuhoaminen. Tämän syy käsitellään osiossa, joka kattaa heap.
Käännösvaihe¶
Kun moduuli tuodaan, MicroPython kääntää koodin tavukoodiksi, jonka MicroPythonin virtuaalikone (VM) sitten suorittaa. Tavukoodi tallennetaan RAM-muistiin. Kääntäjä itse vaatii RAM-muistia, mutta tämä vapautuu käyttöön käännöksen valmistuttua.
Jos useita moduuleja on jo tuotu, voi syntyä tilanne, jossa RAM-muistia ei ole riittävästi kääntäjän suorittamiseen. Tässä tapauksessa import-lause tuottaa muistipoikkeuksen.
Jos moduuli luo globaaleja olioita tuonnin yhteydessä, se kuluttaa RAM-muistia tuontihetkellä, jolloin se ei ole kääntäjän käytettävissä myöhemmissä tuonneissa. Yleisesti on parasta välttää tuonnin yhteydessä suoritettavaa koodia; parempi lähestymistapa on käyttää alustuskoodia, jonka sovellus suorittaa sen jälkeen, kun kaikki moduulit on tuotu. Tämä maksimoi kääntäjän käytettävissä olevan RAM-muistin.
Jos RAM-muisti ei edelleenkään riitä kaikkien moduulien kääntämiseen, yksi ratkaisu on esikääntää moduulit. MicroPythonissa on ristikääntäjä, joka pystyy kääntämään Python-moduuleja tavukoodiksi (katso README mpy-cross-hakemistossa). Tuloksena syntyvällä tavukooditiedostolla on .mpy-pääte; se voidaan kopioida tiedostojärjestelmään ja tuoda tavalliseen tapaan. Vaihtoehtoisesti osa moduuleista tai kaikki moduulit voidaan toteuttaa jäädytettynä tavukoodina: useimmilla alustoilla tämä säästää vielä enemmän RAM-muistia, koska tavukoodi suoritetaan suoraan flash-muistista sen sijaan, että se tallennettaisiin RAM-muistiin.
Suoritusvaihe¶
RAM-muistin käytön vähentämiseen on useita koodaustekniikoita.
Vakiot
MicroPython tarjoaa const-avainsanan, jota voidaan käyttää seuraavasti:
from micropython import const
ROWS = const(33)
_COLS = const(0x10)
a = ROWS
b = _COLS
Molemmissa tapauksissa, joissa vakio sijoitetaan muuttujaan, kääntäjä välttää vakion nimeen kohdistuvan haun koodaamisen korvaamalla sen sen literaaliarvolla. Tämä säästää tavukoodia ja siten RAM-muistia. Kuitenkin ROWS-arvo vie vähintään kaksi konesanaa, yhden kummallekin avaimelle ja arvolle globaalien sanakirjassa. Läsnäolo sanakirjassa on tarpeen, koska toinen moduuli saattaa tuoda tai käyttää sitä. Tämän RAM-muistin voi säästää lisäämällä nimen eteen alaviivan, kuten _COLS: tämä symboli ei ole näkyvissä moduulin ulkopuolella, joten se ei vie RAM-muistia.
const()-funktion argumentti voi olla mitä tahansa, mikä käännösaikana evaluoituu vakioksi, esim. 0x100, 1 << 8 tai (True, "string", b"bytes") (katso yksityiskohdat alla olevasta osiosta). Se voi sisältää jopa muita jo määriteltyjä const-symboleja, esim. 1 << BIT.
Vakiotietorakenteet
Kun vakiotietoa on huomattava määrä ja alusta tukee suoritusta flash-muistista, RAM-muistia voidaan säästää seuraavasti. Tieto tulee sijoittaa Python-moduuleihin ja jäädyttää tavukoodina. Tieto on määriteltävä bytes-olioina. Kääntäjä ”tietää”, että bytes-oliot ovat muuttumattomia, ja varmistaa, että oliot pysyvät flash-muistissa sen sijaan, että ne kopioitaisiin RAM-muistiin. struct-moduuli voi auttaa muunnoksissa bytes-tyyppien ja muiden Pythonin sisäänrakennettujen tyyppien välillä.
Jäädytetyn tavukoodin vaikutuksia harkittaessa on huomattava, että Pythonissa merkkijonot, liukuluvut, tavut, kokonaisluvut, kompleksiluvut ja monikot ovat muuttumattomia. Tämän mukaisesti nämä jäädytetään flash-muistiin (monikoiden osalta vain, jos kaikki niiden alkiot ovat muuttumattomia). Niinpä rivillä
mystring = "The quick brown fox"
varsinainen merkkijono ”The quick brown fox” sijaitsee flash-muistissa. Suoritusaikana viittaus merkkijonoon sijoitetaan muuttujaan mystring. Viittaus vie yhden konesanan. Periaatteessa pitkää kokonaislukua voitaisiin käyttää vakiotiedon tallentamiseen:
bar = 0xDEADBEEF0000DEADBEEF
Kuten merkkijonoesimerkissä, suoritusaikana viittaus mielivaltaisen suureen kokonaislukuun sijoitetaan muuttujaan bar. Tämä viittaus vie yhden konesanan.
Vakio-olioiden monikot ovat itse vakioita. Tällaiset vakiomonikot kääntäjä optimoi siten, ettei niitä tarvitse luoda suoritusaikana joka kerta, kun niitä käytetään. Esimerkiksi:
foo = (1, 2, 3, 4, 5, 6, 100000, ("string", b"bytes", False, True))
Tämä koko monikko on olemassa yhtenä oliona (mahdollisesti flash-muistissa, jos koodi on jäädytetty), ja siihen viitataan aina, kun sitä tarvitaan.
Tarpeeton olioiden luominen
On useita tilanteita, joissa olioita saatetaan luoda ja tuhota tahattomasti. Tämä voi heikentää RAM-muistin käytettävyyttä pirstoutumisen kautta. Seuraavat osiot käsittelevät tämän esimerkkejä.
Merkkijonojen yhdistäminen
Tarkastele seuraavia koodinpätkiä, joiden tavoitteena on tuottaa vakiomerkkijonoja:
var = "foo" + "bar"
var1 = "foo" "bar"
var2 = """\
foo\
bar"""
Kukin tuottaa saman lopputuloksen, mutta ensimmäinen luo tarpeettomasti kaksi merkkijono-oliota suoritusaikana ja varaa lisää RAM-muistia yhdistämistä varten ennen kolmannen tuottamista. Muut suorittavat yhdistämisen käännösaikana, mikä on tehokkaampaa ja vähentää pirstoutumista.
Kun merkkijonoja on luotava dynaamisesti ennen niiden syöttämistä virtaan, kuten tiedostoon, RAM-muistia säästyy, jos tämä tehdään paloittain. Sen sijaan, että luotaisiin suuri merkkijono-olio, luo osamerkkijono ja syötä se virtaan ennen seuraavan käsittelyä.
Paras tapa luoda dynaamisia merkkijonoja on merkkijonon format()-metodin avulla:
var = "Temperature {:5.2f} Pressure {:06d}\n".format(temp, press)
Puskurit
Käytettäessä laitteita, kuten UART-, I2C- ja SPI-rajapintojen instansseja, ennalta varattujen puskureiden käyttö välttää tarpeettomien olioiden luomisen. Tarkastele näitä kahta silmukkaa:
while True:
var = spi.read(100)
# process data
buf = bytearray(100)
while True:
spi.readinto(buf)
# process data in buf
Ensimmäinen luo puskurin jokaisella kierroksella, kun taas toinen käyttää uudelleen ennalta varattua puskuria; tämä on sekä nopeampaa että tehokkaampaa muistin pirstoutumisen kannalta.
Tavut ovat pienempiä kuin kokonaisluvut
Useimmilla alustoilla kokonaisluku vie neljä tavua. Tarkastele kolmea kutsua funktioon foo():
def foo(bar):
for x in bar:
print(x)
foo([1, 2, 0xff])
foo((1, 2, 0xff))
foo(b'\1\2\xff')
Ensimmäisessä kutsussa kokonaislukujen list luodaan RAM-muistiin joka kerta, kun koodi suoritetaan. Toinen kutsu luo vakio-tuple-olion (tuple, joka sisältää vain vakio-olioita) osana käännösvaihetta, joten se luodaan vain kerran ja on tehokkaampi kuin list. Kolmas kutsu luo tehokkaasti bytes-olion, joka kuluttaa vähimmäismäärän RAM-muistia. Jos moduuli jäädytettäisiin tavukoodina, sekä tuple- että bytes-olio sijaitsisivat flash-muistissa.
Merkkijonot vastaan tavut
Python3 toi Unicode-tuen. Tämä toi eron merkkijonon ja tavutaulukon välille. MicroPython varmistaa, etteivät Unicode-merkkijonot vie lisätilaa, kunhan kaikki merkkijonon merkit ovat ASCII-merkkejä (eli niiden arvo on < 128). Jos tarvitaan koko 8-bittisen alueen arvoja, voidaan käyttää bytes- ja bytearray-olioita varmistamaan, ettei lisätilaa tarvita. Huomaa, että useimmat merkkijonometodit (esim. str.strip()) pätevät myös bytes-instansseihin, joten Unicoden poistaminen voi olla kivutonta.
s = 'the quick brown fox' # A string instance
b = b'the quick brown fox' # A bytes instance
Kun on tarpeen muuntaa merkkijonojen ja tavujen välillä, voidaan käyttää str.encode()- ja bytes.decode()-metodeja. Huomaa, että sekä merkkijonot että tavut ovat muuttumattomia. Mikä tahansa operaatio, joka ottaa syötteeksi tällaisen olion ja tuottaa toisen, edellyttää vähintään yhden RAM-varauksen tuloksen tuottamiseksi. Alla olevalla toisella rivillä varataan uusi bytes-olio. Tämä tapahtuisi myös, jos foo olisi merkkijono.
foo = b' empty whitespace'
foo = foo.lstrip()
Kääntäjän suoritus suoritusaikana
Python-funktiot eval ja exec käynnistävät kääntäjän suoritusaikana, mikä vaatii huomattavan määrän RAM-muistia. Huomaa, että micropython-lib-kirjaston pickle-kirjasto käyttää exec-funktiota. Olioiden sarjallistamiseen voi olla RAM-muistin kannalta tehokkaampaa käyttää json-kirjastoa.
Merkkijonojen tallentaminen flash-muistiin
Python-merkkijonot ovat muuttumattomia, joten ne voidaan mahdollisesti tallentaa vain luku -muistiin. Kääntäjä voi sijoittaa flash-muistiin Python-koodissa määritellyt merkkijonot. Kuten jäädytettyjen moduulien kanssa, tietokoneella on oltava kopio lähdepuusta sekä työkaluketju laiteohjelmiston koontia varten. Menettely toimii, vaikka moduuleja ei olisi täysin virheenkorjattu, kunhan ne voidaan tuoda ja suorittaa.
Moduulien tuonnin jälkeen suorita:
micropython.qstr_info(1)
Kopioi ja liitä sitten kaikki Q(xxx)-rivit tekstieditoriin. Tarkista ja poista rivit, jotka ovat ilmeisen virheellisiä. Avaa tiedosto qstrdefsport.h, joka löytyy hakemistosta ports/stm32 (tai käytetyn arkkitehtuurin vastaavasta hakemistosta). Kopioi ja liitä korjatut rivit tiedoston loppuun. Tallenna tiedosto, koonna uudelleen ja flashaa laiteohjelmisto. Lopputulos voidaan tarkistaa tuomalla moduulit ja antamalla uudelleen:
micropython.qstr_info(1)
Q(xxx)-rivien pitäisi olla poissa.
Keko¶
Kun suorituksessa oleva ohjelma luo olion, tarvittava RAM-muisti varataan kiinteäkokoisesta varannosta, joka tunnetaan kekona. Kun olio poistuu näkyvyysalueelta (toisin sanoen tulee koodin saavuttamattomiin), tarpeetonta oliota kutsutaan ”roskaksi”. Prosessi, joka tunnetaan ”roskien keruuna” (GC), ottaa tämän muistin takaisin ja palauttaa sen vapaaseen kekoon. Tämä prosessi toimii automaattisesti, mutta se voidaan käynnistää suoraan antamalla gc.collect().
Aiheen käsittely on jokseenkin monimutkaista. ”Pikaratkaisuksi” anna seuraava ajoittain:
gc.collect()
gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())
Lisätietoja löytyy alta sekä sisäänrakennetun moduulin gc dokumentaatiosta.
Yksityiskohtia MicroPythonin sisäisistä toiminnoista / kehittäjän näkökulmasta löytyy myös kohdasta Muistinhallinta.
Pirstoutuminen¶
Sanotaan, että ohjelma luo olion foo ja sitten olion bar. Tämän jälkeen foo poistuu näkyvyysalueelta, mutta bar jää. foo-olion käyttämä RAM-muisti otetaan takaisin GC:n toimesta. Kuitenkin jos bar oli varattu korkeampaan osoitteeseen, foo-oliosta takaisin saatu RAM-muisti on käyttökelpoista vain olioille, jotka eivät ole suurempia kuin foo. Monimutkaisessa tai pitkään käynnissä olevassa ohjelmassa keko voi pirstoutua: vaikka RAM-muistia on käytettävissä huomattava määrä, yhtenäistä tilaa ei ole riittävästi tietyn olion varaamiseen, ja ohjelma kaatuu muistivirheeseen.
Edellä esitetyt tekniikat pyrkivät minimoimaan tämän. Kun tarvitaan suuria pysyviä puskureita tai muita olioita, on parasta luoda nämä varhain ohjelman suorituksen aikana ennen kuin pirstoutumista voi tapahtua. Lisäparannuksia voidaan tehdä seuraamalla keon tilaa ja hallitsemalla GC:tä; nämä esitetään alla.
Raportointi¶
Käytettävissä on useita kirjastofunktioita, jotka raportoivat muistin varauksesta ja hallitsevat GC:tä. Nämä löytyvät gc- ja micropython-moduuleista. Seuraava esimerkki voidaan liittää REPL:ssä (Ctrl-E siirtyäksesi liittämistilaan, Ctrl-D suorittaaksesi sen).
import gc
import micropython
gc.collect()
micropython.mem_info()
print('-----------------------------')
print('Initial free: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc()))
def func():
a = bytearray(10000)
gc.collect()
print('Func definition: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc()))
func()
print('Func run free: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc()))
gc.collect()
print('Garbage collect free: {} allocated: {}'.format(gc.mem_free(), gc.mem_alloc()))
print('-----------------------------')
micropython.mem_info(1)
Edellä käytetyt metodit:
gc.collect()Pakota roskien keruu. Katso alaviite.micropython.mem_info()Tulosta yhteenveto RAM-muistin käytöstä.gc.mem_free()Palauta vapaan keon koko tavuina.gc.mem_alloc()Palauta tällä hetkellä varattujen tavujen määrä.micropython.mem_info(1)Tulosta taulukko keon käytöstä (yksityiskohdat alla).
Tuotetut luvut riippuvat alustasta, mutta voidaan nähdä, että funktion määrittely käyttää pienen määrän RAM-muistia kääntäjän tuottaman tavukoodin muodossa (kääntäjän käyttämä RAM-muisti on otettu takaisin). Funktion suorittaminen käyttää yli 10 KiB, mutta palatessa a on roskaa, koska se on poistunut näkyvyysalueelta eikä siihen voida viitata. Lopullinen gc.collect() palauttaa tämän muistin.
micropython.mem_info(1)-funktion tuottama lopullinen tuloste vaihtelee yksityiskohdiltaan, mutta sitä voidaan tulkita seuraavasti:
Symboli |
Merkitys |
|---|---|
. |
vapaa lohko |
h |
alkulohko |
= |
jatkolohko |
m |
merkitty alkulohko |
T |
monikko |
L |
lista |
D |
sanakirja |
F |
liukuluku |
B |
tavukoodi |
M |
moduuli |
S |
merkkijono tai tavut |
A |
bytearray |
Kukin kirjain edustaa yhtä muistilohkoa, lohkon ollessa 16 tavua. Niinpä kukin keon vedoksen rivi edustaa 0x400 tavua eli 1 KiB RAM-muistia.
Roskien keruun hallinta¶
GC voidaan vaatia milloin tahansa antamalla gc.collect(). On hyödyllistä tehdä tämä määräajoin, ensinnäkin ennaltaehkäisemään pirstoutumista ja toiseksi suorituskyvyn vuoksi. GC voi kestää useita millisekunteja, mutta se on nopeampi, kun tehtävää on vähän (noin 1 ms OpenMV Cam -kamerassa). Eksplisiittinen kutsu voi minimoida tämän viiveen ja varmistaa samalla, että se tapahtuu ohjelman kohdissa, joissa se on hyväksyttävää.
Automaattinen GC käynnistyy seuraavissa olosuhteissa. Kun varausyritys epäonnistuu, suoritetaan GC ja varausta yritetään uudelleen. Vain jos tämä epäonnistuu, poikkeus nostetaan. Toiseksi automaattinen GC käynnistyy, jos vapaan RAM-muistin määrä laskee kynnysarvon alapuolelle. Tätä kynnysarvoa voidaan mukauttaa suorituksen edetessä:
gc.collect()
gc.threshold(gc.mem_free() // 4 + gc.mem_alloc())
Tämä käynnistää GC:n, kun yli 25 % tällä hetkellä vapaasta keosta tulee varatuksi.
Yleisesti moduulien tulisi luoda data-oliot suoritusaikana käyttäen konstruktoreita tai muita alustusfunktioita. Syynä on, että jos tämä tapahtuu alustuksen yhteydessä, kääntäjältä saattaa loppua RAM-muisti, kun myöhempiä moduuleja tuodaan. Jos moduulit kuitenkin luovat dataa tuonnin yhteydessä, tuonnin jälkeen annettu gc.collect() lievittää ongelmaa.
Merkkijono-operaatiot¶
MicroPython käsittelee merkkijonoja tehokkaalla tavalla, ja tämän ymmärtäminen voi auttaa suunnittelemaan mikrokontrollereissa toimivia sovelluksia. Kun moduuli käännetään, useita kertoja esiintyvät merkkijonot tallennetaan vain kerran, prosessi joka tunnetaan merkkijonojen internoimisena. MicroPythonissa internoitu merkkijono tunnetaan nimellä qstr. Normaalisti tuodussa moduulissa tämä yksittäinen instanssi sijaitsee RAM-muistissa, mutta kuten edellä kuvattiin, tavukoodina jäädytetyissä moduuleissa se sijaitsee flash-muistissa.
Myös merkkijonojen vertailut suoritetaan tehokkaasti tiivisteen avulla merkki merkiltä -vertailun sijaan. Merkkijonojen käytöstä kokonaislukujen sijaan koituva haitta voi siten olla pieni sekä suorituskyvyn että RAM-muistin käytön kannalta - tosiasia, joka saattaa yllättää C-ohjelmoijat.
Jälkikirjoitus¶
MicroPython välittää, palauttaa ja (oletuksena) kopioi oliot viittauksen kautta. Viittaus vie yhden konesanan, joten nämä prosessit ovat tehokkaita RAM-muistin käytön ja nopeuden suhteen.
Kun tarvitaan muuttujia, joiden koko ei ole tavu eikä konesana, on olemassa standardikirjastoja, jotka voivat auttaa tallentamaan nämä tehokkaasti ja suorittamaan muunnoksia. Katso array-, struct- ja uctypes-moduulit.
Alaviite: gc.collect()-paluuarvo¶
Unix- ja Windows-alustoilla gc.collect()-metodi palauttaa kokonaisluvun, joka ilmaisee keruussa takaisin saatujen erillisten muistialueiden lukumäärän (tarkemmin sanottuna vapaiksi muutettujen alkulohkojen lukumäärän). Tehokkuussyistä bare metal -portit eivät palauta tätä arvoa.