Forum: Compiler & IDEs Strukturen verwenden?


von Rudi (Gast)


Lesenswert?

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

von Pako (Gast)


Lesenswert?

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).

von Udo S. (urschmitt)


Lesenswert?

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.

von Meisl (Gast)


Lesenswert?

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.

von Bronco (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

> 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.

von Rudi (Gast)


Lesenswert?

@ 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" :-)

von Pako (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

> 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.

von Bronco (Gast)


Lesenswert?

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.

von gcc (Gast)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.