Hallo,
ich hab gerade mein USART Modul mit einem Empfangs- und Sende FIFO
ausgestattet.
1
typedef struct
2
{
3
char *data;
4
uint8_t head;
5
uint8_t tail;
6
uint8_t size;
7
}buffer_t;
Außerdem habe ich eine Funktion zum Lesen- und Beschreiben des Puffers:
1
char GetByteFromBuffer(buffer_t *buffer);
2
void WriteByteToBuffer(buffer_t *buffer, char c);
Jetzt wird mein Code aber exorbitant groß! Über 200 Byte kostet mich der
Spaß...
Woran das liegt weis ich eigentlich auch. einmal muss die Struktur
dereferenziert werden, beim Zugriff auf den data-Member muss dieser
nochmal dereferenziert werden (dafür kann ich so unterschiedliche
Puffergrößen einrichten).
Außerdem übergebe ich den zu verwendenen Puffer by Reference, also auch
nochmal dereferenzieren...
Jetzt die Frage:
Würdet ihr die Rechenzeit- und Speicherplatzeinbußen in Kauf nehmen
zugunsten der Lesbarkeit, Erweiterbarkeit und Übersichtlichkeit?
Ich hab mir natürlich andere Librarys angesehn. Diese sind teilweise
hochoptimiert, zB Peter Fleurys. Hier werden nur einzelne, fest codierte
Variablen benutzt.
Oft sind auch nur Puffergrößen 2^n erlaubt um Zeit und Platz zu sparen.
Aber für was sind denn Strukturen dann gedacht? Sollte ich nicht Wert
darauf legen, meinen Code sauber und strukturiert anzulegen?
Zusammengehörige Elemente zu gruppieren?
Achja, an Speicher mangelts mir nicht da ich nen ATmega1284P benutze.
Ein konkretes Projekt hab ich auch nicht, mir gehts eher um den
Lerneffekt.
Wie steht Ihr zu sowas?
Viele Grüße,
Rudi
Rudi schrieb:> Würdet ihr die Rechenzeit- und Speicherplatzeinbußen in Kauf nehmen> zugunsten der Lesbarkeit, Erweiterbarkeit und Übersichtlichkeit?
Ich behaupte mal, daß sich das nicht widerspricht!
Man kann problemlos strukturiert und nachvollziehbar programmieren, ohne
die Ressourcen überzustrapazieren. Allerdings würde ich im Zweifel immer
einen strukturierten Stil vorziehen, denn ein hochoptimierter
Spaghetti-Code, den man selber nicht mehr versteht, nützt nichts, wenn
man ihn nicht mehr nachvollziehen und debuggen kann.
Ich programmiere den AVR in C++ mit Klassen, und habe dadurch keine
nennenswerte Ressourcen-Nachteile - vorausgesetzt, man macht es richtig
(z.B. an den richtigen Stellen statische Klassen einsetzen).
Dir ist klar, daß in deiner Struktur noch kein Platz für die Daten ist?
Du hast nur einen Zeiger auf den Datenbereich (char * data) definiert,
aber noch kein Platz für die Daten selbst.
Da ich annehme, daß du das nicht dynamisch allokieren willst vermute ich
mal das das so nicht gewollt ist.
Ich würde Data einfach als Arry definieren mit einer
Konstantendefinition für die Größe.
Warum dein Code 200 Byte größer ist liegt NICHT an dem gezeigten. Zeige
den ganzen Code (kompilierbar als Datei(en) anhängen) denn werden dir
die AVR Spezis bestimmt helfen.
Danke schon mal an Pako, sehe ich ähnlich, ich bevorzugs auch gerne
übersichtlich, aber eure Meinungen dazu interessieren mich :-)
@ Udo Schmitt: Natürlich beinhaltet meine Struktur nur einen Zeiger auf
einen C-String. Diesen muss ich natürlich extra anlegen, nach dem Motto:
1
char rxBufferData[RX_BUFFER_SIZE]
2
buffer_t rxBuffer;
3
4
void init_buffers()
5
{
6
rxBuffer.data = rxBufferData;
7
rxBuffer.head = 0;
8
rxBuffer.tail = 0:
9
rxBuffer.size = rxBufferData[RX_BUFFER_SIZE];
10
}
Vorteil: Jeder Buffer kann eine unterschiedliche Größe haben, weist aber
die selben Schnittstellen auf.
Und wenn ich 1000 verschieden große Buffer anlegen will geht das mit
minimalen Aufwand.
Deswegen der char* in der struct anstatt ein char[].
Im übrigen gehts mir nicht darum, meinen Code auf Teufel komm raus auf
Speicherplatz zu optimieren.
Ich weis, wo der Speicher hinverschwindet. Und ich weis was ich dagegen
tun könnte.
Beispiel: FIFO ist ein Ringspeicher, also benötigen wir nach dem
Schreiben eine Überprüfung, ob der letzte Index erreicht wurde:
1
buffer->head++;
2
if (buffer->head >= buffer->size)
3
{
4
buffer->head = 0;
5
}
Sowas ist natürlich ein Albtraum für den Compiler.
(Wobei man natürlich je nach Anzahl der Zugriffe auf die Member diese
auch lokal zwischenspeichern könnte)
Die Frage war eher, wie ihr das handhabt. Bevorzugt ihr die
Frickellösung, mit der ihr zwar den Atmel bis aufs letzte Bit ausreitzt,
der Code aber nur von euch (und auch nur die nächsten 2 Monate :-))
verstanden werden kann?
Hier im Zusammenhang mit Struktur-Zeigern ists mir halt besonders stark
aufgefallen, dass man viel Zeit und Platz sparen könnte wenn man auf
"höhere" Konstruktionen, die C zur Verfügung stellt verzichtet.
Meisl schrieb:> Beispiel: FIFO ist ein Ringspeicher, also benötigen wir nach dem> (...)> Sowas ist natürlich ein Albtraum für den Compiler.> (Wobei man natürlich je nach Anzahl der Zugriffe auf die Member diese> auch lokal zwischenspeichern könnte)
Ich fürchte, Du hast da eine Sache nicht ganz verstanden:
1. Wenn Du nur einen Ringpuffer benötigst, brauchst Du keinen Zeiger,
sondern kannst eine statische Strukturbenutzen.
1
rxBuffer.head++;
2
if(rxBuffer.head>=rxBuffer.size)
3
{
4
rxBuffer.head=0;
5
}
Dann sparst Du Dir die Zeigerei.
2. Wenn Du mehrere Ringpuffer benötigst, mußt Du eh irgendwie
unterscheiden, welcher bearbeitet wird, und da ist ein Zeiger die
eleganteste Möglichkeit.
3. Wenn Du nur einen Ringpuffer brauchst und trotzdem einen Zeiger
verwendest, dann hast Optimierungspotential.
> dass man viel Zeit und Platz sparen könnte
Na komm.
Übertreib mal nicht.
200 Bytes im Flash ist jetzt im Normalfall nichts, was mir schlaflose
Nächte bereitet.
Das einzige was mich verblüfft: Wenn ich im Kopf so durchgehe, was eine
FIFO so zu tun hat, kommen mir die 200 Bytes als hoch vor. Von daher
wäre der konkrete Code interessant um den mal durch den Compiler zu
jagen und zu sehen, wieso der Optimizer die Memberzugriffe nicht besser
zusammenfasst.
Aus dem Bauch raus hätt ich nämlich gesagt: Du hast den Optimizer nicht
eingeschaltet.
@ Karl Heinz Buchegger:
Danke dir schonmal.
Wenn ich daheim bin kann ich den Code gerne mal posten, momentan hab ich
den aber nicht zur Hand.
Das Codeschnippsel dass ich oben gepostet habe frisst, wenn ichs richtig
in Erinnerung habe, 25 Byte (habs versuchsweise gestern mal
auskommentiert).
Der Teil ist 2 mal nötig, einmal für head (schreiben) und einmal für
tail (lesen).
Dazu noch die Prüfung auf Überlauf beim Schreiben und Unterlauf beim
Lesen (buffer->head == buffer->tail).
Außerdem noch der eigentliche Lesevorgang und der Schreibvorgang.
Grob überschlagen kommt das ziemlich gut an 200 Byte hin.
Ich werd mal nur aus Interesse das ganze statisch mit festen, einzelnen
Variablen lösen.
@ Bronco: Ich hab doch im Eröffnungspost bereits erwähnt, dass ich einen
Sende- und einen Empfangspuffer verwende.
Natürlich kann ich mir die Zeigerei auch hier sparen, indem ich eigene
Funktionen für den RX und TX Puffer schreibe.
Die Momentane Lösung ist halt generischer, ob das notwendig ist sei mal
dahingestellt (wobei das garnichtmal so abwegig ist, zB Portierung des
USART Moduls auf einen Prozessor mit 2, 4, x USARTs)
Ahja, und nochmal: das Beispiel mit dem USART und dem Puffer ist nur mal
ein konkretes Beispiel wo mir dieses Verhalten sehr stark aufgefallen
ist.
Meine eigentliche Fragestellung lässt sich reduzieren auf "optimierter
Frickelcode vs. eleganten strukturierten (aufgeblassenen) Code" :-)
Rudi schrieb:> Meine eigentliche Fragestellung lässt sich reduzieren auf "optimierter> Frickelcode vs. eleganten strukturierten (aufgeblassenen) Code" :-)
Man kann auch mit Spaghetti-Code sehr ineffizient programmieren.
> Meine eigentliche Fragestellung lässt sich reduzieren auf> "optimierter Frickelcode vs. eleganten strukturierten> (aufgeblassenen) Code" :-)
Dann kann die Antwort nur lauten:
Machs erst richtig
dann miss nach
und wenn dann noch Optimierungsbedarf besteht, dann optimiere.
Und ob der elegant strukturierte Code tatsächlich aufgeblasen ist, das
würde ich erst mal nicht als gegeben annehmen. Bei größeren Systemen ist
nämlich oft das Gegenteil der Fall: der optimierte Frickelcode ist in
Wirklichkeit gar nicht so optimiert, wie der Programmierer das glaubt.
Karl Heinz Buchegger schrieb:> Bei größeren Systemen ist> nämlich oft das Gegenteil der Fall: der optimierte Frickelcode ist in> Wirklichkeit gar nicht so optimiert, wie der Programmierer das glaubt.
Ich war mal auf einem Programmierkurs für den Infineon TriCore, dessen
Compiler aufgrund seiner Instruction-Pipeline ziemlich heftig optimiert.
Damals war der O-Ton:
Beim TriCore optimiert keiner besser als der Compiler, also kann man
sich als Programmierer auf die Aufgabenstellung konzentrieren.
Ich schätze mal die 200 Bytes kommen von der stdlib die du für malloc
einbinden musst. Die sind nämlich der Heap der am hinter .bss generiert
wird. Hat also erstmal nichts mit performance zu tun.