Szeregowanie kooperacyjne w Micropythonie na ESP8266.

Ku mojemu zdziwieniu Micropython na esp8266 nie obsługuje wątków, a potrzebna mi była funkcja „sleep()”, która będzie się wykonywać równolegle do głównego wątku. Dziwne to, bo z FreeRTOS SDK wątki ponoć działają, choć całkiem możliwe, że kosztują zbyt wiele cennych zasobów potrzebnych Micropythonowi do życia. Znalazłem też info, że wynika to z ograniczeń zestawu instrukcji procesora. Cóż, FreeRTOS działa super na esp32 i niech tak zostanie. Niestety bez wątków funkcja zawłaszcza procesor licząc czas w pętli i jak chciałoby się wcześniej zasterować podłączonymi do esp8266 ustrojstwami, to trzeba by to zrobić manualnie ;). Na szczęście dostępna ilość pamięci pozwala na użycie modułu uasyncio i nieblokujących się socketów, bo to też istotne jeśli chcemy obsługiwać sprzęt przez requesty http typu <adres_ip>/onoff czy <adres_ip>/sleep=czas. Jedyny minus, to upip, którym nie zainstalujemy uasyncio ze względu na zbyt małą ilość pamięci. Trzeba to zrobić ręcznie.

import network
import usocket as socket
import errno

sta_if = network.WLAN(network.STA_IF)
addr = socket.getaddrinfo(sta_if.ifconfig()[0], 8000)[0][-1]
s = socket.socket()
s.setblocking(False)
s.bind(addr)
s.listen(1)
print('listening on', addr)

import machine
pin = machine.Pin(5, machine.Pin.OUT)
status = True; timeout = None
pin.value(status)

import uasyncio as asyncio
loop = asyncio.get_event_loop()

async def sleep(newtm):
    global status, timeout
    if timeout == newtm:
        return
    if timeout != None:
        timeout = newtm
        return
    else:
        timeout = newtm
    while timeout > 0:
        await asyncio.sleep(60)
        timeout = timeout - 60
    status = False; timeout = None
    pin.value(status)

async def worker():
    global status
    while True:
        line = None
        try:
            cl, addr = s.accept()
            print('client connected from', addr)
            cl_file = cl.makefile('rwb', 0)
            while not line:
                line = cl_file.readline()
        except OSError as exc:
            if exc.args[0] in [errno.ETIMEDOUT, errno.EAGAIN]:
                await asyncio.sleep(0)
                continue
        print(line, status)
        if 'onoff' in line:
            status = not status
            pin.value(status)
        if 'sleep' in line:
            s1 = line.find(b'sleep')+6
            s2 = line.find(b'H')-1
            try:
                timeout = int(line[s1:s2])
                loop.create_task(sleep(timeout*60))
            except ValueError:
                print('/sleep needs a numeric timeout')
        cl.close()

loop.create_task(worker())
loop.run_forever()

# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4

Ten sam skrypt na esp32 z użyciem _thread. Esp32 to już jednak potężna bestia, z ogromną ilością pamięci, zwłaszcza z PSRAM na pokładzie, oraz sporym zapasem mocy procesora dual-core ;).

Reklamy

STMint on Mist ;)

tpy0 tpy1

MIST w trybie STEroids, 8MB RAM, Emutos 0.9 (na 0.9.3 nie działa, ale powalczę jeszcze). ST High działa w trybie mono (w color mi się nie udało).

Niestety Tinypy jest trochę powolne, nawet w STEroids. W porównaniu z nim stary Python1.3 z oficjalnej paczki działa super szybko, ale nie poddaje się z próbami kompilacji Micropythona i Pymite (Python-on-chip) ;). Nie ma się co dziwić – w Aranym, który emuluje Falcona z 68040, Python 2.6.4 i Tinypy działają niemal z tą samą prędkością. Różnica STMint vs Freemint/Aranym jest jednak kolosalna: 7.5/0.11 = 68.18 razy wolniej…

timeI teraz chyba najciekawsze spostrzeżenie: Mist w STEroids jest szybszy niż emulowane w Hatari TT@32Mhz. Na kolejnych screenach: TT@8Mhz, TT@16Mhz i TT@32Mhz.TT@8MHzTT@16MHzTT@32MHz

Tinypy dla FreeMint.

Jak widać po ostatnich wpisach eksperymentowałem z Pythonem na FreeMint. Binarka dużego Pythona jest dostępna tutaj i działa bardzo ładnie (patrz poprzedni wpis). Szukałem cały czas małego interpretera (z myślą o Mist) i udało mi się skompilować Tinypy:
tinypy

Binarkę tinypy dla FreeMint wrzuciłem tutaj. Muszę ją jeszcze pod Mist-em przetestować ;).

Update: na ST Mint działa:

pystmint Więc na Mist też zadziała (po cichu liczę, że może w trybie STEroids).

Python vs Cython vs PyPy.

Moich tendencyjnych eksperymentów ciąg dalszy. Zrezygnowałem z Numpy i jestem pod wrażeniem memoryviews w Cythonie. Poniższe to tak na prawdę test wydajności adresowania tablic jednowymiarowych.

Python:

import sys
import random

def fib(n):
    a, b = 0, 1
    for i in range(n):
        a, b = b, a + b
    return a

def showfib(n):
    r = []
    for i in range(n):
        t = fib(random.randint(0,49))
        r.append(t)
        print(t, end=" ")
    print()

if __name__ == '__main__':
    n=int(sys.argv[1])
    showfib(n)

Cython:

#cython: language_level=3, boundscheck=False

import sys
from libc.stdio cimport printf
from libc.stdlib cimport rand, srand
from cython.view cimport array
from time import time

cdef unsigned int fib(int m) nogil:
    cdef unsigned int a, b, i
    a, b = 0, 1
    for i in range(m):
        a, b = b, a + b
    return a

cdef showfib(n):
    cdef r = array(shape=(n,), itemsize=sizeof(unsigned int), format="I")
    cdef unsigned int [:] rv = r

    cdef int i
    cdef unsigned int t
    for i in range(n):
        t = fib(rand()%49)
        rv[i] = t
        printf("%u ", t)
    printf("\n")

if __name__ == '__main__':
    n=int(sys.argv[1])
    srand(int(time()))
    showfib(n)

Python-3.4.2:
$ time python fib.py 300000 > /dev/null
real 0m7.564s
user 0m7.543s
sys 0m0.013s

PyPy3-2.4 (portable):
$ time pypy fib.py 300000 > /dev/null
real 0m1.838s
user 0m1.813s
sys 0m0.020s

Cython-0.21.1:
$ time ./fib 300000 > /dev/null
real 0m0.189s
user 0m0.190s
sys 0m0.000s

Cython vs Python: 40,02 x szybciej.
Pypy vs Python: 4,11 x szybciej.
Cython vs PyPy: 9,72 x szybciej.