PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : PROGMEM und Optimierung



Ceos
08.10.2013, 14:23
Hardware ATXMEGA64 @ 32MHz

Ich sitze hier an einem sehr interssantem Thema und habe auch schon einiges an Tatsachen hervorgeholt und wollte mal eure Meinung dazu hören.

Ich habe eine quasi Konstante Tabelle (Array) const somestruct_t data[SOMESIZE] = {...} welche ich per PROGMEM in den ROM befördert habe

Nun stellt sich das Problem dar, dass ich von extern einen 16bit Wert erhalte den ich mit einem 16bit Wert innerhalb der Struktur vergleichen muss um anschließend je nach Lage die ein oder andere Funktion via FPointer aufzurufen.

ich habe insgesamt 4 Ansätze probiert und das Disassembly dazu studiert um die Laufzeit zu ermitteln

1. memcpy_P in eine statische tempvariable welche ich in einem if() vergleiche -> 32 Zyklen

2. direkter memcmp_P im if() -> 28 Zyklen

3. if(pgm_read_word() == extern) -> 25 Zyklen

4. lookup im RAM über die Keys -> 9 Zyklen und später dann Zugriff auf die Pointer (in den Fällen 1-3 würde exakt dasselbe passieren)

die Schleife sieht im moment unschön aus und soweit ich das Disassembly überschaue ist sie quasi nicht optimiert worden!

Mein erster Gedanke war es den Vergleich zu optimieren (Option 3 oder 4) und aus der SChleife heraus einen Call zu starten um die passende Reaktion zu starten und dem Compiler das optimieren zu überlassen.

Option 5: Mein zweiter Gedanken jedoch ging in die Richtung einen unschönen Switch() aufzubauen, ihn aber per Precompiler strukturiert zu definieren, da ich im moment für die Tabelle eh schon relativ viel funktionspointer einsetze!

Seht ihr aus der sicht extremer RAM knappheit und eingeschränkter ROM Verfügbarkeit(mehr ROM als RAM aber nicht sehr viel mehr) noch andere Optionen oder Ansätze? Kann man eventuell noch anders Daten im ROM ablegen und effizient darüber iterieren welche ich vielleicht übersehen habe? Oder hat jemand noch einen Assembler Ansatz in der Hinterhand?

MfG

Ceos

Wsk8
08.10.2013, 15:45
Seh ich das richtig, dass dir diese ~25 Zyklen für den Zugriff zu lange sind oder worauf willst du hinaus?

mfg

Ceos
08.10.2013, 18:03
korrekt, ich versuche gerade eine ISR zu optimieren, leider ist der restliche Teil des Programms relativ komplex und eine Neugestaltung erscheint mir aus derzeitiger Sicht unmöglich sonst hätte ich schon am Design optimert, aber es ist mir nun leider im Moment vorgegeben dass ich über eine PROGMEM Tabelle zu suchen habe um einen geeignete Reaktion auszulösen .... die eigentliche ISR ist weit größer und unnötig kompliziert weswegen keien Optimierung gelingt, zumindest daran kann ich was ändern wenn ich die Schleifen entsprechend umgestalte!

Am liebsten wäre mir die Tabelle durch ein, per Precompiler übersichtlich gestaltetes, Switch Case zu ersetzen um dem Compiler die Optimierung voll und ganz anzuvertrauen!

54 Einträge mit jeweils mind. 50 Zyklen für PROGMEM Zugriffe auf diese Einträge sehe ich kritisch wenn man versucht nebenbei einen 200us Cycle-Prozess laufen zu lassen und im worst case 84us nur für das Durchlaufen der Tabelle draufgehen

Wsk8
08.10.2013, 18:26
Wie siehts mit externer HW aus? Vlt nen extra RAM-IC?

Ansonsten kann ich dir nur raten, dein Design zu überprüfen. Eine ISR sollte immer sehr kurz gehalten werden. Hört sich bei dir jetzt nicht so kurz an.

mfg

Ceos
08.10.2013, 19:53
"dein Design zu überprüfen" das ist der Haken :) ich fress mich gerade durch das Programm weils eben nicht mein Design ist und versuch es zu schmieren! Und nochmals back to Topic bitte, ich hatte schon mehrere Ansätze gebracht und hoffte auf ein wenig Feedback zu denen oder vielleicht doch noch eine optimalere Lösung ...

Die Kommunikation ist nicht anders als wie oben beschriebe zu lösen im Moment, das Timing ist und bleibt kritisch und der timed Prozess hat schon exklusiv höhere prio als die com ISR! Trotzdem muss ich die Com ISR in einem gewissen zeitrahmen bearbeiten und mich durch eine commandliste fressen welche im moment ineffizient in einem progmem array steckt

Wsk8
08.10.2013, 20:18
Was sind denn das für Werte in der Tabelle? 1, 2, 3, ... oder völlig Random und wie entstehen die?

mfg

Ceos
09.10.2013, 07:15
es ist ein Befehlssatz und die Anordnung ist nicht zwingend Sortiert oder in irgend einer Art fortlaufend, im Moment habe ich versucht den Worst-Case zu unterdrücken indem ich die Befehle in der Initialisierungstabelle einfach nach Aufruf-Häufigkeit und nachfolgendem Rechenaufwand sortiert habe(Rechenaufwand = "decodierung" von Parametern <kurzwierig> oder instant-response <langwierig>, z.B.), aber das kann nicht das Maß der Dinge sein dass ich an der Datenbasis tunen muss! Ich tune lieber die Verarbeitung.

Einen Teilerfolg habe ich ja schonmal mittels 108Byte RAM erkauft indem ich eine LookUp dort hinterlegt habe und einfach den Index synchron halte mit der Tabelle was aber nicht Failsafe ist! Und die 108Byte RAM schmerzen gewaltig wenn ich mir das alle so ansehe. Und es werden noch einige Befehle hinzukommen was wieder jeweils 2 Byte weniger RAM bedeutet.

Die Hardware ist im übrigen bereits ausgereizt, da gibt es keine Luft mehr. Aber ich habe mir vorgenommen, wenn ich mal meine eigenen Vorstellungen umsetzen darf, dass ich das EBI Interface des XMega zu meinem Vorteil nutzen werde! 64MHz Bustakt und Memory Mapping sollten mit einem guten Framework-Design die Möglichkeiten erweitern ... aber auch die Platinengröße :<

[NACHTRAG]
okay in meinem disassembly ist mir ein feines aber BÖSES detail entgangen, ich habe eine "CALL" answeisung ignoriert welche mir überraschenderweise bei memcpy und memcmp zusätzliche 28 zyklen serviert ... ich denke mit der ersparnis durch pgm_read_word() sollte sich die Lage schon einmal gewaltig entspannen. Statt 32+28 bzw. 28+28 nur 25 Zyklen scheint mir schon ausreichend und würde auch erklären warum die Zyklen Anzahl sich im Praxistest als nicht nachvollziehbar darstellte, jetzt ergeben meine Zeitmessungen auch wesentlich mehr sinn! Mittlerweilen bin ich mir auch ziemlich sicher dass bei allen 3 Varianten mit progmem die Endianess mir einen Strich durch die Rechnung macht oder wie kann man folgendes Disassembly sinnvoll erklären ?

000010C9 1 MOVW R24,R16 Copy register pair
000010CA 1 MOVW R22,R24 Copy register pair
000010CB 1 LSL R22 Logical Shift Left
000010CC 1 ROL R23 Rotate Left Through Carry
000010CD 1 LSL R24 Logical Shift Left
000010CE 1 ROL R25 Rotate Left Through Carry
000010CF 1 LSL R24 Logical Shift Left
000010D0 1 ROL R25 Rotate Left Through Carry
000010D1 1 LSL R24 Logical Shift Left
000010D2 1 ROL R25 Rotate Left Through Carry
000010D3 1 ADD R22,R24 Add without carry
000010D4 1 ADC R23,R25 Add with carry
000010D5 1 SUBI R22,0x5F Subtract immediate
000010D6 1 SBCI R23,0xFD Subtract immediate with carry
000010D7 1 MOVW R30,R22 Copy register pair
000010D8 3 LPM R24,Z+ Load program memory and postincrement
000010D9 3 LPM R25,Z Load program memory
000010DA 1 CP R24,R10 Compare
000010DB 1 CPC R25,R11 Compare with carry
000010DC 2 BRNE PC-0x1A Branch if not equal



ja jetzt wird mir einiges klarer

if(pgm_read_word(&table[i].someval) == input)
er muss ja erst den offset der Variable im Struct umrechnen bzw. den Offset im Array ... ich sollte manchmal nicht einfach das übernehmen was mir vorgegeben wurde :)

uint16_t *ptr = &table[i].someval;
if(pgm_read_word(ptr) == input)
das sollte auch unnötige Takte bei der späteren Verarbeitung sparen!

[NACHTRAG]

const uin16_t ptr= &progmemtable[i].someval;
000010C8 MOVW R24,R16 Copy register pair
000010C9 MOVW R30,R24 Copy register pair
000010CA LSL R30 Logical Shift Left
000010CB ROL R31 Rotate Left Through Carry
000010CC LSL R24 Logical Shift Left
000010CD ROL R25 Rotate Left Through Carry
000010CE LSL R24 Logical Shift Left
000010CF ROL R25 Rotate Left Through Carry
000010D0 LSL R24 Logical Shift Left
000010D1 ROL R25 Rotate Left Through Carry
000010D2 ADD R30,R24 Add without carry
000010D3 ADC R31,R25 Add with carry
000010D4 SUBI R30,0x5F Subtract immediate
000010D5 SBCI R31,0xFD Subtract immediate with carry
if(pgm_read_word(ptr) == input)
000010D6 LPM R24,Z+ Load program memory and postincrement
000010D7 LPM R25,Z Load program memory
000010D8 CP R24,R10 Compare
000010D9 CPC R25,R11 Compare with carry
000010DA BRNE PC-0x18 Branch if not equal

und schon spare ich mir runde 14Takte bei folgeaufrufen auf die Variable ... leider muss ich mir jeden Wert einzeln pointern

Frage: gib es einen weg die Adresse eines Wertes innerhalb eines Structs auf Basis eine Pointers auf den Anfang selbigen Structs zu ermitteln?


tyble_t *ptr = &table[i];
if(pgm_read_word(ptr+offset(someval)) == input)

Antwort: JA und es ist optimiert sich quasi von allein weg!


table_t *pt = &progmemtable[i];
000010C8 MOVW R22,R16 Copy register pair
000010C9 LSL R22 Logical Shift Left
000010CA ROL R23 Rotate Left Through Carry
000010CB MOVW R24,R16 Copy register pair
000010CC LSL R24 Logical Shift Left
000010CD ROL R25 Rotate Left Through Carry
000010CE LSL R24 Logical Shift Left
000010CF ROL R25 Rotate Left Through Carry
000010D0 LSL R24 Logical Shift Left
000010D1 ROL R25 Rotate Left Through Carry
000010D2 ADD R22,R24 Add without carry
000010D3 ADC R23,R25 Add with carry
000010D4 MOVW R30,R22 Copy register pair
000010D5 SUBI R30,0x5F Subtract immediate
000010D6 SBCI R31,0xFD Subtract immediate with carry
if(pgm_read_word(pt+offsetof(table_t,someval)) == input)
000010D7 LPM R24,Z+ Load program memory and postincrement
000010D8 LPM R25,Z Load program memory
000010D9 CP R24,R10 Compare
000010DA CPC R25,R11 Compare with carry
000010DB BRNE PC-0x19 Branch if not equal




und bevor hier jetzt sowas kommt von wegen "das hätte ich auch gewusst, ist ja teil von stdlib" ... zeige mir den nicht autistischen menschen der aus dem stehgreif die ganze stdlib ohne nachschlagen oder google herbeten kann :D da gibt es mehr zu wissen als man wieder vergessen kann und was man nicht alltäglich verwendet vergisst man nach der schule ganz schnell wieder ... ich hab meinen post bewusst so gelassen damit jemand der etwas ähnliches sucht den gedankengang später nachvollziehen kann ...

und zum thema Design .... Design ist ne Frage der Sicht auf die Bäume ... mal sieht man den Wald mal nicht, kommt drauf an wie tief man drinsteckt


Schade ... gerade eben habe ich erfahren dass offsetof() laut misra verboten ist weil unzuverlässig ... bleibt wohl doch nur das redesign