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 ;).

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.

Pypy, Cython, mingw-w64, static libpython3.4m.a.

Mój powrót do Pythona po kilkuletniej przerwie skończył się fascynacją. Po pierwsze Pypy – ale to szybkie i całkowicie zgodne z Pythonem! Po drugie Cython (opcja –embed) szok, udało mi się skompilować statyczne binarki skryptu aconv.py dla Linuksa i Windows (walczę jeszcze trochę z kodowaniem konsoli cmd w Windows, bo tu oczywiście są niemałe problemy z 16bit kodowaniem – muszę zrozumieć PyUnicode w Pythonie i Cythonie). Po trzecie: konfiguracja i działanie MSYS z mingw i mingw-w64 – niesamowity toolchain (instalowałem z win-builds), wszystko działa jak na Linuksie, trzeba tylko skonfigurować /c/mingw/msys/1.0/etc/fstab, czyli dodać c:\mingw /mingw, uruchamiać C:\MinGW\msys\1.0>msys.bat a potem konfigurację mingw-w64 (uwaga na kropkę) . /c/mingw/msys/1.0/opt/windows_32/bin/win-builds-switch 32 najlepiej dodać do ~/.bash_profile, ścieżka do gcc z mingw-w64 i bibliotek będzie się sama ustawiać po odpaleniu msys.bat.

Kompilację Pythona 3.4.2 ze statyczną i dynamiczną biblioteką libpython3.4m udostępniłem tutaj). Zbudowany po nałożeniu łatek z repo mingw-w64 i wykonaniu autoreconf-2.68.

W powyższym zipie zmodyfikowałem python/include/pyconfig.h w taki sposób, aby po dodaniu do wywołania gcc opcji -DPy_ENABLE_STATIC -static linkował z libpython3.4m.a, a bez tych opcji z libpython3.4m.dll:

#if defined Py_ENABLE_STATIC
#undef Py_ENABLE_SHARED
#else
#define Py_ENABLE_SHARED 1
#endif

PS: Aby interaktywny interpreter Pythona z tej kompilacji zadziałał, trzeba uruchomić polecenie:
. /c/mingw/msys/1.0/opt/windows_32/bin/win-builds-switch 32 w przeciwnym razie będzie mu brakować bibliotek.

Konwersja ATASCII/ UTF-8.

Na podstawie tablicy konwersji z patcha Mono do libiconv, napisałem skrypt w Pythonie do konwersji do i z ATASCII. Może się komuś przyda :).

#!/usr/bin/python3
import sys

if len(sys.argv) != 3:
    print("Usage:", sys.argv[0], "-u (to unicode) || -a (to atascii)")
    exit(0)

d = {0: 9829, 1: 9500, 2: 9621, 3: 9496, 4: 9508, 5: 9488, 6: 9585, 7: 9586, 8: 9698,
    9: 9623, 10: 9699, 11: 9629, 12: 9624, 13: 9620, 14: 9601, 15: 9622, 16: 9827,
    17: 9484, 18: 9472, 19: 9532, 20: 9679, 21: 9604, 22: 9615, 23: 9516, 24: 9524,
    25: 9612, 26: 9492, 27: 9243, 28: 8593, 29: 8595, 30: 8592, 31: 8594, 32: 32,
    33: 33, 34: 34, 35: 35, 36: 36, 37: 37, 38: 38, 39: 39, 40: 40, 41: 41, 42: 42,
    43: 43, 44: 44, 45: 45, 46: 46, 47: 47, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52,
    53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 58: 58, 59: 59, 60: 60, 61: 61, 62: 62,
    63: 63, 64: 64, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72,
    73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82,
    83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 91: 91, 92: 92,
    93: 93, 94: 94, 95: 95, 96: 9830, 97: 97, 98: 98, 99: 99, 100: 100, 101: 101,
    102: 102, 103: 103, 104: 104, 105: 105, 106: 106, 107: 107, 108: 108, 109: 109,
    110: 110, 111: 111, 112: 112, 113: 113, 114: 114, 115: 115, 116: 116, 117: 117,
    118: 118, 119: 119, 120: 120, 121: 121, 122: 122, 123: 9824, 124: 124, 125: 8624,
    126: 9664, 127: 9654, 128: 9829, 129: 9500, 130: 9609, 131: 9496, 132: 9508,
    133: 9488, 134: 9585, 135: 9586, 136: 9700, 137: 9627, 138: 9701, 139: 9625,
    140: 9631, 141: 9607, 142: 9601, 143: 9628, 144: 9827, 145: 9484, 146: 9472,
    147: 9532, 148: 9679, 149: 9600, 150: 9615, 151: 9516, 152: 9524, 153: 9616,
    154: 9492, 155: 10, 156: 8593, 157: 8595, 158: 8592, 159: 8594, 160: 9608,
    161: 33, 162: 34, 163: 35, 164: 36, 165: 37, 166: 38, 167: 39, 168: 40, 169: 41,
    170: 42, 171: 43, 172: 44, 173: 45, 174: 46, 175: 47, 176: 48, 177: 49, 178: 50,
    179: 51, 180: 52, 181: 53, 182: 54, 183: 55, 184: 56, 185: 57, 186: 58, 187: 59,
    188: 60, 189: 61, 190: 62, 191: 63, 192: 64, 193: 65, 194: 66, 195: 67, 196: 68,
    197: 69, 198: 70, 199: 71, 200: 72, 201: 73, 202: 74, 203: 75, 204: 76, 205: 77,
    206: 78, 207: 79, 208: 80, 209: 81, 210: 82, 211: 83, 212: 84, 213: 85, 214: 86,
    215: 87, 216: 88, 217: 89, 218: 90, 219: 91, 220: 92, 221: 93, 222: 94, 223: 95,
    224: 9830, 225: 97, 226: 98, 227: 99, 228: 100, 229: 101, 230: 102, 231: 103,
    232: 104, 233: 105, 234: 106, 235: 107, 236: 108, 237: 109, 238: 110, 239: 111,
    240: 112, 241: 113, 242: 114, 243: 115, 244: 116, 245: 117, 246: 118, 247: 119,
    248: 120, 249: 121, 250: 122, 251: 9824, 252: 124, 253: 8624, 254: 9664, 255: 9654}

mode=0

if sys.argv[1] == '-u':
 f = open(sys.argv[2], 'rb')
 output=str()
 mode=1
else:
 f = open(sys.argv[2], 'r', encoding='utf-8')
 output=bytearray()

def overwrite():
  if mode==1:
   f = open(sys.argv[2], 'w', encoding='utf-8')
  else:
   f = open(sys.argv[2], 'bw')
  f.write(output)
  f.close()

lines = f.read()
f.close()

if mode==1:
 for i in lines:
  if i in d:
   output+=chr(d[i])

if mode == 0:
 import collections
 sd = collections.OrderedDict(sorted(d.items()))
 dinv = {}
 for k,v in sd.items():
  if v not in dinv:
   dinv[v] = k
 for i in lines:
  if ord(i) in dinv:
   output.append(dinv[ord(i)])

overwrite()

A tutaj statyczne binarki iconv dla Linuksa i Windows z najnowszym patchem dodającym kodowania:

ATARI ATARIST RISCOS-LATIN1 ATARI8 ATARI8-ATASCII ATARI8-GRAPH ATASCII ATARI8-ATASCII2 ATARI8-INT  ATASCII2 ATARI8-KAREN ATARI8-PL ATARI8-AWP ATARI8-XLENT ATARI8-PANTHER ATARI8-PE ATARI8-CAPEK ATARI8-CZ ATARI8-TCHEKO ZX80 ZX 81 ZX82 ZXSPECTRUM ZX82-PL ZXSPECTRUM-PL PETSCII PETSCII-UC PETSCII-LC CP867 CP895 KAMENICKY  KEYBCS2