Worum geht es? Ein 128x64 monochrom OLED. Der Bildspeicher besteht also aus (128*64)/8 Bytes.
Jedes Bit repräsentiert den Zustand von einem Pixel.
Das macht es sehr aufwändig (=langsam), wenn mann z.B. einen rechteckigen Displayinhalt von A nach B kopieren will.
Für jeden einzelnen Pixel von der x/y Position ausgehend das entsprechende Byte vom Bildspeicher suchen, dort das konkrete Bit suchen, lesen... und zum Schreiben an der Zielposition das Ganze nochmal.
Das erscheint mir extrem ineffizient. (Und laut LA dauert es wirklich "ewig".)
Gibt es da irgendeine Abkürzung oder einen Trick?
Wenn es sich um 8 Bit Blöcke handelt (man also das Display in ein 8-Pixel Raster zerlegt), könnte man z.B. ganze Bytes einfach von A nach B kopieren ohne die Bitpfriemelei. Nachteil: gas geht nur mit Vielfachen von 8.
Hat jemand eine Idee für einen Workarround bzw. performanteres lesen/schreiben/kopieren?
Alles was man vorher berechnen kann aus der inneren Schleife rausziehen. Du läufts ja wahrscheinlich mit einem Zähler über die Y-Richtung und darin geschachtelt mit nem anderen Zähler über die X-Richtung. Ich denke da kann man sich die Multiplikation mit SSD1306_LCDWIDTH pro Pixel sparen, wenn man das einmal vor der inneren Schleife berechnet und dann in der inneren Schleife nur noch inkrementiert
Das "y/8" durch nen Shiftbefehl " >> 3" ersetzen sofern der Compiler das nicht schon von alleine tut
Vielleicht kann man innere Schleifen 'unrollen' also ausprogrammieren anstatt über 8 Bits zu iterieren. Da du hier aber meistens nicht genau auf Bytegrenzen liegts wird das schwierig.
Es gibt aber bestimmt ein Algorithmus der die innere Schleife mit zwei mal Byte kopieren, shiften, ausmaskieren und zusammenfügen schafft
Wie gesagt, mit nem fertigen Algorithmus kann ich nicht dienen aber man kämpft sich eigentlich immer erstmal durch innere Schleifen wenn man anfängt sowas zu optimieren.
Du hast doch gute Kontakte zu dem Entwickler(n) von FastLED ich denke der kann sowas relativ schnell in Assembler hinzaubern wenn erstmal das ganze in C optimiert ist.
Danke für Deine Antwort, Jarny. Ja, die existierende Implementation hat definitiv Optimierungspotential.
Ich frage mich, ob es nicht einen besseren Designansatz gibt, v.a. wenn man genug RAM übrig hat.
Z.B. denke ich darüber nach, jedes Bit in einem ganzen Byte zu speichern, welche man sehr schnell (und direkt) handlen kann und erst am Ende aller Read/Write Operationen diesen Buffer wieder auf 1/8 zu komprimieren und zum Display zu schicken.
Da könnte man die Byteblöcke mit einer getunten memcopy Funktion bearbeiten (welche FastLED dabeihat). Also nur zeilenweise die 2 Startadressen (Quelle und Ziel) berechnen und dann x Bytes von A nach B kopieren.
Ich vermute, das spart erheblich Zeit, v.a. wenn man viel Kleinkram liest/schreibt. Was denkst Du darüber?
Das Problem beim "freien" Kopieren ist ja tatsächlich, dass man eine geschachtelte Schleife fürs Lesen UND fürs Schreiben braucht und jedesmal das "bitweise oder" drin hat.
Jarny:
Vielleicht kann man innere Schleifen 'unrollen' also ausprogrammieren anstatt über 8 Bits zu iterieren. Da du hier aber meistens nicht genau auf Bytegrenzen liegts wird das schwierig.
In diesem Fall werden innerhalb der Schleife so viele Rechenoperationen gemacht werden dass der Overhead nicht allzu groß sein wird. Das ist eher ein Problem wenn man in einer Schleife kaum was macht.
Kannst du nicht erst die Anzahl der Bits die Vielfache von 8 sind behandeln und dann den Rest extra? Also z.B. bei 50 Bits, macht man 50 / 8 = 6 und 50 % 8 = 2. So hat man 6 * 8 Bits und dann nochmal 2 Bits einzeln
Kannst du nicht erst die Anzahl der Bits die Vielfache von 8 sind behandeln und dann den Rest extra? Also z.B. bei 50 Bits, macht man 50 / 8 = 6 und 50 % 8 = 2. So hat man 6 * 8 Bits und dann nochmal 2 Bits einzeln
Gute Idee! Allerdings ist die Startposition auch variabel. Könnte also z.B. bedeuten 1 Bit, 6 Bytes und nochmal ein Bit.
Am Ziel das Gleiche. Gibt es einen schnellen Weg für ein bitweises memcopy?
Ich sehe das Problem darin, dass z.B. das 2. Bit vom von mir genannten Beispiel (also der Anfang vom 1. Byte) im Ziel z.B. das 5. Bit sein muss und von dort an fortlaufend.
Wie könnte man das handlen? In allen Bytes die Bits schieben?
Z.B. denke ich darüber nach, jedes Bit in einem ganzen Byte zu speichern, welche man sehr schnell (und direkt) handlen kann und erst am Ende aller Read/Write Operationen diesen Buffer wieder auf 1/8 zu komprimieren und zum Display zu schicken.
Ich sehe das Problem darin, dass z.B. das 2. Bit vom von mir genannten Beispiel (also der Anfang vom 1. Byte) im Ziel z.B. das 5. Bit sein muss und von dort an fortlaufend.
Das ist natürlich doof
Wie könnte man das handlen? In allen Bytes die Bits schieben?
Was hälst Du von diesem Ansatz?
Möglich wäre das alles. Was aber am schnellsten ist könnte ich so nicht sagen.
Wenn du Bits schieben willst solltest du dir vielleicht eine Union aus einem Array aus unsigned long und einem Array aus Byte anlegen. Dann kann man die unsigned long schieben. Auf einem 8 Bit Prozessor sind das immer noch mehrere Operationen, aber da man in Assembler durch das Carry Bit rotieren kann, sollte das immer noch einigermaßen schnell gehen. Oder bist du auf einem ARM? Dann ginge das schneller, da 32 Bit Operationen.
An der Grenze zu 4 Bytes musst du natürlich immer noch per Hand ein Bit von einem UL und den nächsten übertragen! Ich habe mal eine Bitset Klasse geschrieben die das macht.
Helmuth:
Ich frage mich, ob es nicht einen besseren Designansatz gibt, v.a. wenn man genug RAM übrig hat.
Z.B. denke ich darüber nach, jedes Bit in einem ganzen Byte zu speichern, welche man sehr schnell (und direkt) handlen kann und erst am Ende aller Read/Write Operationen diesen Buffer wieder auf 1/8 zu 1komprimieren und zum Display zu schicken.
Wenn du so viel Speicher hast (bzw. verschwenden willst^^) , dann kann man auch drüber nachdenken sich 8 Versionen des Originals (jeweils schon vorher um 1 Bit verschoben) im Speicher zu halten. Dann musst du dir beim kopieren quasi nur heraussuchen welches (verschobene) Bild du kopieren möchtest, kannst das dann aber zeilenweise Byte für Byte kopieren anstatt mit den aufwendigen Bitoperationen. Das bringt ordentlich was. Nur an den Rändern links und rechts musst du noch ausmaskieren und kombinieren.
Kurzer Zwischenbericht: Der Displayspeicher ist so organisiert, dass ein Byte 8 vertikale Pixel repräsentiert. Der Index läuft dagegen horizontal. Das ist sehr gut für mich.
Das bedeutet konkret, dass ich mir für mein horizontales Scrolling die Bitschieberei erstmal sparen kann und stattdessen bequem und schnell Bytes kopiere.
Habe nicht konkret gemessen, aber das ist auf Anhieb (unoptimiert) mindestens 3x so schnell, wie vorher.
Ganz allgemein würde ich alle betroffenen Bytes in einer Zeile bzw. Spalte ins RAM lesen, dann auf die gewünschte Ausgabeposition shiften, und am Zielort ausgeben. Die Bits im ersten und letzten Byte müssen dabei mit den verbleibenden Pixeln des Displays gemischt werden. Und das dann für alle Spalten bzw. Zeilen des Bereichs.
Und aufpassen daß nichts überschrieben wird, was nachher noch kopiert werden sollte! Meist braucht man zwei unterschiedliche Schleifen, die den Bereich von vorne bzw. von hinten kopieren können.