cut_image

Python is the language that I write most of my code in and cPython is the interpreter that I use. That’s quite typical: python is cPython 99% of the time. But what is that small fraction of other python interpreters? Today I am featuring micropython: an independent project that fits python language into tiny (few kB or MB) bare metal platforms.

Billions of small devices around us have only few Kb of memory. The project demonstrates that few Kb is still enough to run python interpreter: it is not just a proof of a concept but rather do-your-usual-microcontroller-stuff-in-python concept.

Despite being micro, micropython delivers most of modern python experience. There are some parts of the language that are not implemented (or rather shortcut). At the same time, micropython features unique extensions that you do not find elsewhere! This post is about the micropython.viper decorator enabling maximal speed for integer buffer manipulations.

Viper

Python interpreters are known for being slow, sometimes too slow. In cPython you can use various tools and “native” interfaces to speed up performance-critical parts. Same, in principle, applies to micropython. Your best guess is to write performance-critical code in C and to link it into firmware. But there is also a tiny built-in compiler which you use towards machine-fast code called Viper. To enable it, you only need to put @viper decorator on top of the target function. For example, this is what I have in my pywatchos.

@micropython.viper
def min_max(fr: int, val: int, to: int) -> int:
    if val < fr:
        return fr
    if val > to:
        return to
    return val

The above code will be translated into few machine instructions rather than python opcodes making it blazing fast. A cython analogue would look as following.

def min_max(int fr, int val, int to):
    if val < fr:
        return fr
    if val > to:
        return to
    return val

Cython does undergo multiple parsing, translations and linking stages. @viper does not have most of it (which also means that you can barely implement complex logic). But memory buffer operations is where viper can shine. For example, the following function swaps even and odd bytes.

@micropython.viper
def swap16(dst: ptr8, src: ptr8, size: int):
    for i in range(0, size, 2):
        tmp = src[i]
        dst[i] = src[i + 1]
        dst[i + 1] = tmp

I use it to convert between the big-endian BMP image file format and the little-endian ESP32 platform. swap16 is more than 10 times faster than the corresponding pure-python analogue which will perform type checks multiple times per line.

This is one of the many reasons to learn about micropython. Build it from source, pull it , run it inside docker or in your browser.