Skip to content

Performance de

github-actions[bot] edited this page Sep 9, 2026 · 13 revisions

Performance

English · Deutsch

Das Zeichenbudget für alle, die einen Bildschirm hinzufügen oder ändern.

Die Randbedingung

Die Frame Rate dieses Panels begrenzt die Bandbreite des PSRAM (Pseudo-Static Random-Access Memory), nicht die CPU (Central Processing Unit). Die LCD-Einheit (Liquid-Crystal Display) liest ununterbrochen einen Framebuffer mit etwa 30 MB/s aus dem PSRAM, und die Framebuffer liegen hinter einem Write-Back-, Write-Allocate-Datencache (64 KB, 8-fach assoziativ, 64-Byte-Lines). Jedes Pixel, das die CPU schreibt, kostet einen 64-Byte-Line-Fill und ein 64-Byte-Write-Back, sofern die Line nicht schon geladen ist. Die Kosten eines Frames sind deshalb eine Anzahl von Cache-Line-Fills, und die lässt sich auf dem Host exakt messen.

tools/frame_cost.py baut die echten Bildschirme für den Host und lässt sie unter cachegrind mit der Cache-Geometrie des ESP32-S3 laufen. Es meldet die Differenz zwischen einem und elf gerenderten Frames, sodass Prozessstart und das erste Füllen jedes Buffers herausfallen.

$ python3 tools/frame_cost.py
panel 39.0 Hz, ~39 MB/s effective -> 976 KiB of traffic per panel frame

mode       lines/frame     traffic   est. ms  est. fps
-------------------------------------------------------
frame            8,856     1107 KiB     29.1      19.5
frame-idle        1,274      159 KiB      4.2      39.0
held             4,777      597 KiB     15.7      39.0
sim              9,817     1227 KiB     32.2      19.5
throttle        10,598     1325 KiB     34.8      19.5
chrome          33,106     4138 KiB    108.7       7.8
overview           953      119 KiB      3.1      39.0
servo           15,759     1970 KiB     51.7      13.0
servo-grip        3,026      378 KiB      9.9      39.0
analyser           871      109 KiB      2.9      39.0
logs               937      117 KiB      3.1      39.0
settings           862      108 KiB      2.8      39.0
battery            871      109 KiB      2.9      39.0
balance            865      108 KiB      2.8      39.0
programmer          895      112 KiB      2.9      39.0
balance-sim        2,395      299 KiB      7.9      39.0
settings-sim        2,407      301 KiB      7.9      39.0
battery-sim        2,401      300 KiB      7.9      39.0
analyser-chrome       39,234     4904 KiB    128.8       6.5
logs-chrome       16,061     2008 KiB     52.7      13.0
settings-chrome       23,591     2949 KiB     77.4       9.8
battery-chrome       36,987     4623 KiB    121.4       7.8
balance-chrome       40,685     5086 KiB    133.5       6.5
programmer-chrome       28,480     3560 KiB     93.5       9.8
picker             902      113 KiB      3.0      39.0
picker-chrome       15,883     1985 KiB     52.1      13.0
clear           12,006     1501 KiB     39.4      19.5
vlines           8,160     1020 KiB     26.8      19.5
hlines               0        0 KiB      0.0      39.0
Modus Was er misst
frame der Motorprüfstand auf einem Frame, in dem ein Telemetriesample eintrifft
frame-idle der Motorprüfstand auf einem Frame zwischen zwei Samples, ohne Berührung
held der Motorprüfstand zwischen zwei Läufen: live Anzeigen über einem Plot, der den letzten hält
sim wie frame, mit dem SIMULATION-Watermark
throttle der Motorprüfstand mit einem Finger am Gas, der Drag-Fall
chrome der Motorprüfstand ohne Cache, vollständig neu gezeichnet
overview das Menü, Chrome gecacht
servo der Servobildschirm mit neu gezeichnetem Arm
servo-grip der Servobildschirm, nur der Griff neu gezeichnet
analyser, logs, settings, battery, balance, programmer, picker ein ruhiger Frame dieses Bildschirms, Chrome gecacht
<screen>-sim derselbe Bildschirm mit dem SIMULATION-Watermark
<screen>-chrome derselbe Bildschirm, auf jedem Frame invalidiert
clear ein Löschen des ganzen Bildschirms
vlines siebzehn senkrechte Linien über die volle Höhe
hlines dieselbe Pixelzahl als waagerechte Linien

Die absoluten Zahlen verschieben sich zwischen Maschinen um einige Fills, weil argv und die Umgebungsvariablen denselben Cache belegen wie der Framebuffer. frame_cost.py --check-doc prüft die Tabelle deshalb mit einer Toleranz von 1 %.

Regeln

Zeilenweise zeichnen. Siebzehn senkrechte Linien über die volle Höhe kosten 8 160 Fills; dieselbe Pixelzahl als waagerechte Linien kostet null, weil jede Line vom vorigen Pixel noch geladen ist. Die Oberfläche ist in waagerechte Bänder gegliedert, und eine senkrechte Trennlinie ist eine bewusste Ausgabe.

Das Chrome cachen. Alles neu zu zeichnen kostet etwa das Dreißigfache des eingeschwungenen Zustands. Jeder Bildschirm führt je Framebuffer eine Bitmaske dessen, was er schon gezeichnet hat; dafür ist das Argument buffer_index von render() da. Das Panel wechselt zwischen zwei Buffern; ein Bildschirm, der nur den gerade gezeichneten Buffer invalidiert, lässt den anderen einen Frame zurück, was als Flackern erscheint.

Ein Stencil, das sich nicht bewegt, wird gecacht. SIMULATION wird auf jedem Frame gezeichnet, sobald die Prüfstandswerte LINK_BN_SIMULATED tragen, also auch bei einem Coprozessor, der mit simulierten Werten antwortet. Gedrehter Text scannt seine gedrehte Bounding-Box, von Ecke zu Ecke also die ganze Canvas, und rotiert und dividiert je Pixel, um die 3 439 Pixel zu schreiben, die das Watermark bedeckt: 0,9 % der Canvas. ui_watermark nimmt diese Punkte einmal auf und schreibt sie danach nur noch, mit 144 721 Instruktionen je Frame statt 8 412 078.

Eine Zahl von Fills ist keine Zahl von Zyklen. Die Tabelle oben misst Cache-Line-Fills, und das Watermark kostet davon nur 1 586: es ist Arithmetik je Pixel, kein Traffic. Es kostete das 58-Fache dessen, was die Tabelle nahelegte, und die Tabelle konnte das nicht zeigen. Wo die gemessene Frame-Zeit eines Modus über dem liegt, was seine Fills vorhersagen, erst Instruktionen zählen, dann der Schätzung trauen.

Ein Bedienelement, das sich jeden Frame bewegt, bekommt seinen eigenen Zähler. Ein Drag liefert eine Berührung je Frame. Die Buttons und das Gas des Motorbildschirms teilten eine Revision, weshalb ein Drag jedes Bedienelement daneben neu zeichnete. Das Gas hat jetzt seinen eigenen und zeichnet die Box der Anzeige und die bemalte Region des Sliders neu: Der Modus throttle in der Tabelle oben misst das gegen einen Sample-Frame. Der Flip quantisiert auf ganze Panel-Frames: ein Drag liegt entweder innerhalb von zwei davon oder wartet auf einen dritten, also 19,5 fps oder 13,0. Auf der Hardware mit 50,5 ms gemessen, nahm der alte Drag den dritten.

Zu löschen ist, was ein Widget bemalt, nicht was es einnimmt. Der Daumen des Sliders und sein Schatten stehen 5 px oben und 7 px unten über den Track hinaus, und die Hero-Ziffern sind 32 px hoch mit 3 px Schräge auf einer 30 px hohen Zeile. Ein Clear in der Grösse des Tracks oder der Zeile lässt daher an jeder Position, die der Finger passiert hat, einen Daumen und die Füsse der Ziffern stehen. ui_slider_painted_rect() liefert die Region, statt sie den Aufrufer herleiten zu lassen.

Nur auf Frames zeichnen, die etwas zu zeichnen haben. Samples kommen mit 20 Hz, das Panel zeichnet mit 39 Hz, also hat etwa jeder zweite Frame nichts Neues. Der Prüfstandsbildschirm führt den Push-Zähler des Plots und eine Revisionsnummer der Bedienelemente, jeweils je Framebuffer, und zeichnet Plot, Anzeigen und Bedienelemente nur neu, wenn der zugehörige Zähler sich bewegt hat. each_framebuffer_is_updated_independently in test_motor hält die Zähler je Buffer fest.

Obergrenzen

Das Panel bewegt 976 KiB je Frame bei 39 Hz. Ein Frame, der das Doppelte kostet, landet bei 19,5 fps (Frames pro Sekunde), also einem Frame je 20-Hz-Telemetriesample; schneller zu zeichnen würde identische Pixel neu malen, langsamer würde Samples verlieren. CI (Continuous Integration) hält jeden Modus an eine Obergrenze:

Modi Obergrenze (Fills) Fängt
frame, sim 15 600 einen Prüfstandsframe, der ein Telemetriesample überschreitet
overview 2 000 einen Bildschirm mit gecachtem Chrome, der neu zu zeichnen begonnen hat
servo 17 000 ein Wachsen der Arm- und Griffzeichnung
servo-grip 4 000 ein Atmen, das die ganze Karte neu zeichnet
die sieben Bildschirmmodi 1 200 einen Bildschirm, der neu zu zeichnen begonnen hat
die drei -sim-Modi 2 800 ein Watermark, das über die volle Canvas hinauswächst
die sieben -chrome-Modi 45 000 ein wachsendes vollständiges Neuzeichnen

Braucht ein künftiger Bereich mehr Platz, sind die verbleibenden Hebel vom gröbsten zum feinsten: die Höhe des Plots, seine Breite, und das Simulations-Watermark auf den tatsächlich neu gezeichneten Bereich zu clippen.

Die Logzeile DRAW … WAIT … des ESP32-S3, alle 300 Frames ausgegeben, ist die Prüfung auf der Hardware: DRAW ist die Zeichenzeit, WAIT die Zeit, die der Buffer-Wechsel blockiert hat. Ein gesunder Frame besteht überwiegend aus WAIT.

Clone this wiki locally