
RPi-Pico MicroPython Speed-Test
von Andreas
Elektronik-Labor
Projekte
Mikrocontroller
Raspberry

Ab der Version 1.19.x (ab 06/2022) ist MicroPython sehr viel näher an
Python 3 gerückt. Viele Dinge sind jetzt deutlich besser und schneller
geworden. Eine gute Übersicht bietet das Buch Python 3, Das umfassende
Handbuch von Johannes Ernesti und Peter Kaiser,
https://www.rheinwerk-verlag.de/python-3-das-umfassende-handbuch/.
Neu ist eine Möglichkeit der
schnelleren Funktionsaufrufe, bei der man die Funktion wie eine Variable im RAM
gehalten: read = adc.read_u16.
Diese Programmiermethode nennt man "lookup". In einer
Schleife val = [read() for _ in range(Count)] ist nun die Ausführung wesentlich
schneller, allerdings wird auch viel mehr RAM verbraucht. Auch eine
Zählschleife ohne eigentliche Zählvariable for _ in range(Count) ist neu und
effizienter. Jedoch muss man beachten, dass jede LOOKUP Definition auch RAM
belegt, weil dieser Teil kompiliert im RAM erhalten bleibt. Daher sollte man
wirklich darauf achten, wo wird wirklich Ausführungsgeschwindigkeit benötigt,
damit man sich den RAM nicht permanent zumüllt. Jede Variable / Bounded Methode,
die nur temporär benötigt wird, kann man jederzeit mit "del" wieder
aus dem Arbeitsspeicher entfernen. Eine andere Methode ist, dass man komplexe
Ablaufe in Funktionen (mit "def" beginnend ) auslagert. Jede Variable,
die im Hauptprogrammteil __main__ initialisiert wird, belegt auch oder ab ihrer
Initialisierung RAM. Hingegen sind lokal Variablen, die erst in Funktionen
initiiert werden und werden mit Beendigung der Funktion auch wieder aus dem RAM
entfernt. Ausnahme bildet die hier schon genannte Methode eines Lookup.
Neu ist nun auch die
"F-String-Formatierung", wie sie in den aktuellen Python-Versionen ab
3.6 schon implementiert sind. Weiterhin die Möglichkeit, den Prozessortakt frei
einzustellen. Zum Overclocken muss man zuerst die Einschalt-Clock Frequenz
auslesen, und in einer Variablen die nicht mit "del" gelöscht werden
darf, zwischengespeichert werden. Hier ist jedoch zu beachten, dass der
µPython-Interpreter nur Geschwindigkeiten zwischen 20 und 240 MHz akzeptiert,
wenn eine Konnektivität via USB besteht. In diesem Bereich werden auch die
korrekten Funktionen aller Schnittstellen I²C / SPI / UART / USB sichergestellt.
Im Standalone-Betrieb, bei einer Vcc von 5,0V über PIN 40 oder einer Vcc ab 3,3
V über PIN39 kann man, solange kein Zugriff auf die Bus-Schnittstellen
stattfindet, auch den CPU Takt temporär auf bis zu 480 MHz anheben. Hier muss
man dann allerdings aufpassen, dass man keinen HW-IRQ via Pin.irq, Timer, oder
Statemachine aktiv hat. Um Überhitzungen im Dauerbetrieb zu vermeiden, sollte
man den CPU-Takt wieder auf Default setzen, wenn diese Rechen-Power nicht
benötigt wird, oder einen CPU Kühlkörper verwenden.
Für eine Laufzeitdiagnose kann
man aus der Bibliothek "utime" mit "ticks_us()" (Microsekunden
), "ticks_ms()" (Millisekunden ) und "ticks_cpu()" (CPU-Takte
) nutzen, um sehr genaue Messungen durchzuführen. Diese Funktionen werden
hier benutzt, um die Programmlaufzeiten genauer zu untersuchen und die
Ergebnisse auszuwerten.
from machine import Pin, ADC, freq as CPU_freq
from utime import ticks_us, ticks_diff
def messen(frequency):
print(f'CPU Freq : {(frequency/1_000_000):3.0f} MHz')
start=ticks_us()
val = [read() for _ in range(Count)]
lauf=ticks_diff(ticks_us(), start)
print('Messen mit Funktions-Lookup')
print(f'Laufzeit für {Count:5d} Messungen = {(lauf/1_000):3.3f} ms')
print(f'Sampling: {(1 / ((lauf / Count) / 1_000)):6.1f} KSPS.\n')
del val
start=ticks_us()
val = [adc.read_u16() for _ in range(Count)]
lauf=ticks_diff(ticks_us(), start)
print('Messen ohne Funktions-Lookup')
print(f'Laufzeit für {Count:5d} Messungen = {(lauf/1_000):3.3f} ms')
print(f'Sampling: {(1 / ((lauf / Count) / 1_000)):6.1f} KSPS.\n')
print('-'*40)
del val
Count = 10_000
CPU_Frequency = (20,40,60,80,100,120,125,133,166,200,240)
OLD_CPU = CPU_freq()
adc = ADC(2)
read = adc.read_u16 # Lookup
for clock in CPU_Frequency:
c = int(clock * 1_000_000)
CPU_freq(c)
messen(c)
CPU_freq(OLD_CPU)
Das Ergebnis:
MicroPython v1.19.1 on 2022-06-18; Raspberry Pi Pico with RP2040
Type "help()" for more information.
>>> %Run -c $EDITOR_CONTENT
CPU Freq : 20 MHz
Messen mit Funktions-Lookup
Laufzeit für 10000 Messungen = 469.286 ms
Sampling: 21.3 KSPS.
Messen ohne Funktions-Lookup
Laufzeit für 10000 Messungen = 532.685 ms
Sampling: 18.8 KSPS.
----------------------------------------
CPU Freq : 40 MHz
Messen mit Funktions-Lookup
Laufzeit für 10000 Messungen = 273.374 ms
Sampling: 36.6 KSPS.
Messen ohne Funktions-Lookup
Laufzeit für 10000 Messungen = 277.920 ms
Sampling: 36.0 KSPS.
----------------------------------------
CPU Freq : 60 MHz
Messen mit Funktions-Lookup
Laufzeit für 10000 Messungen = 195.416 ms
Sampling: 51.2 KSPS.
Messen ohne Funktions-Lookup
Laufzeit für 10000 Messungen = 216.443 ms
Sampling: 46.2 KSPS.
----------------------------------------
CPU Freq : 80 MHz
Messen mit Funktions-Lookup
Laufzeit für 10000 Messungen = 154.013 ms
Sampling: 64.9 KSPS.
Messen ohne Funktions-Lookup
Laufzeit für 10000 Messungen = 169.757 ms
Sampling: 58.9 KSPS.
----------------------------------------
CPU Freq : 100 MHz
Messen mit Funktions-Lookup
Laufzeit für 10000 Messungen = 126.824 ms
Sampling: 78.8 KSPS.
Messen ohne Funktions-Lookup
Laufzeit für 10000 Messungen = 139.467 ms
Sampling: 71.7 KSPS.
----------------------------------------
CPU Freq : 120 MHz
Messen mit Funktions-Lookup
Laufzeit für 10000 Messungen = 110.184 ms
Sampling: 90.8 KSPS.
Messen ohne Funktions-Lookup
Laufzeit für 10000 Messungen = 120.679 ms
Sampling: 82.9 KSPS.
----------------------------------------
CPU Freq : 125 MHz
Messen mit Funktions-Lookup
Laufzeit für 10000 Messungen = 106.860 ms
Sampling: 93.6 KSPS.
Messen ohne Funktions-Lookup
Laufzeit für 10000 Messungen = 116.969 ms
Sampling: 85.5 KSPS.
----------------------------------------
CPU Freq : 133 MHz
Messen mit Funktions-Lookup
Laufzeit für 10000 Messungen = 102.429 ms
Sampling: 97.6 KSPS.
Messen ohne Funktions-Lookup
Laufzeit für 10000 Messungen = 112.285 ms
Sampling: 89.1 KSPS.
----------------------------------------
CPU Freq : 166 MHz
Messen mit Funktions-Lookup
Laufzeit für 10000 Messungen = 87.147 ms
Sampling: 114.7 KSPS.
Messen ohne Funktions-Lookup
Laufzeit für 10000 Messungen = 94.719 ms
Sampling: 105.6 KSPS.
----------------------------------------
CPU Freq : 200 MHz
Messen mit Funktions-Lookup
Laufzeit für 10000 Messungen = 76.756 ms
Sampling: 130.3 KSPS.
Messen ohne Funktions-Lookup
Laufzeit für 10000 Messungen = 83.073 ms
Sampling: 120.4 KSPS.
----------------------------------------
CPU Freq : 240 MHz
Messen mit Funktions-Lookup
Laufzeit für 10000 Messungen = 68.628 ms
Sampling: 145.7 KSPS.
Messen ohne Funktions-Lookup
Laufzeit für 10000 Messungen = 73.871 ms
Sampling: 135.4 KSPS.
----------------------------------------
>>>
Der Controller wurde hier bis 240
MHz übertaktet und hat dabei eine Abtastrate von 145,7 Kilosamples pro Sekunde
erreicht. Damit wird mit MicroPython sogar ein Audiosampling mit 96 kSPS
möglich.
Abschlussbemerkung: Auch wenn der RP2040 unter / mit C/C++
Samplingsraten bis 217 kSPS erreicht, sollte man nicht vergessen, dass µPython
eine Interpreter-Sprache ist. Für einfache bis gehobene Regel- und
Steuerungsaufgaben, sowie für Einsteiger-Experimente sogar für
Echtzeitanwendungen, wenn auf User-Events reagiert werden muss ("Pin.irq(trigger
Pin.IRQ_FALLING, ())" oder "Pin.irq(trigger Pin.IRQ_RISING, ())"
) erreicht µPython hier ansprechende Reaktionszeiten im zweistelligen µSek
Bereich. Man beachte auch, dass dieses Test-Programm nur im primären CORE der
CPU ausgeführt wird.
Nachtrag: Ein Programm zur Geschwindigkeitsoptimierung
from utime import ticks_cpu, ticks_diff
print('LIST füllen')
print('Liste mit Werten füllen')
print('List klasssich mit append ergänzen')
start = ticks_cpu()
table = [] # LIST anlegen
for i in range(1_000):
table.append(i)
print(ticks_diff(ticks_cpu(), start), 'CPU Takte\n')
del table
print('Liste über Schachtlung füllen')
start = ticks_cpu()
table = [i for i in range(1_000)]
print(ticks_diff(ticks_cpu(), start), 'CPU Takte\n')
del table
print('Liste mit Platzhaltern füllen')
print('List klasssich mit append ergänzen mit Schlefenvariable')
start = ticks_cpu()
table = [] # LIST anlegen
for i in range(1_000):
table.append(0)
print(ticks_diff(ticks_cpu(), start), 'CPU Takte\n')
del table
print('Liste mit Platzhaltern füllen')
print('List klasssich mit append ergänzen ohne Schlefenvariable')
start = ticks_cpu()
table = [] # LIST anlegen
for _ in range(1_000):
table.append(0)
print(ticks_diff(ticks_cpu(), start), 'CPU Takte')
print('Hier macht eine Schleife ohne Zählvariable keinen Sinn\n')
del table
print('Liste über Schachtlung füllen')
start = ticks_cpu()
table = [0 for _ in range(1_000)]
print(ticks_diff(ticks_cpu(), start), 'CPU Takte')
print('hier macht die Schleife ohne Zählvariable Sinn\n')
del table
# Vorbereitung
table = [i for i in range(1_000)]
print('Werte aus einer LIST heraussortieren mit 1 Parameter')
start = ticks_cpu()
sortierung = []
for value in table :
if value > 738:
sortierung.append(value)
print(ticks_diff(ticks_cpu(), start), 'CPU Takte\n')
del sortierung
print('Sortierung mit 1 Parameter innerhalb einer Schachtlung')
start = ticks_cpu()
sortierung = [value for value in table if value > 500]
print(ticks_diff(ticks_cpu(), start), 'CPU Takte\n')
del sortierung
# If - Else Abfragen
temp = 13.7 # Vorbereitng
print('IF-Else Abfragen in klassicher Aufteilung')
start = ticks_cpu()
if temp > 15:
result = True
else:
result = False
print(ticks_diff(ticks_cpu(), start), 'CPU Takte\n')
print('IF-Else Abfragen in einem Block')
start = ticks_cpu()
result = (True if temp > 15 else False)
print(ticks_diff(ticks_cpu(), start), 'CPU Takte\n')
Ergebnisse:
MicroPython v1.19.1 on 2022-06-18; Raspberry Pi Pico with RP2040
Type "help()" for more information.
>>> %Run -c $EDITOR_CONTENT
LIST füllen
Liste mit Werten füllen
List klasssich mit append ergänzen
12740 CPU Takte
Liste über Schachtlung füllen
3337 CPU Takte
Liste mit Platzhaltern füllen
List klasssich mit append ergänzen mit Schlefenvariable
11177 CPU Takte
Liste mit Platzhaltern füllen
List klasssich mit append ergänzen ohne Schlefenvariable
11210 CPU Takte
Hier macht eine Schleife ohne Zählvariable keinen Sinn
Liste über Schachtlung füllen
3228 CPU Takte
hier macht die Schleife ohne Zählvariable Sinn
Werte aus einer LIST heraussortieren mit 1 Parameter
9192 CPU Takte
Sortierung mit 1 Parameter innerhalb einer Schachtlung
4938 CPU Takte
IF-Else Abfragen in klassicher Aufteilung
67 CPU Takte
IF-Else Abfragen in einem Block
31 CPU Takte