Forum: Compiler & IDEs Codeoptimierung: Codesize bei mehrfachen Funktionsaufrufen


von ---=DIAN=--- (Gast)


Lesenswert?

Hallo,

ich hätte da mal ne Frage zur Optimierung.
ich habe eine "Funktion" die ein Bitmapmuster ans Grafikdisplay schickt:

void Lcd_Write(unsigned char Bytewert,unsigned char Daten)
{
  if (Daten==0) {LCD_A0_PORT&=~(1<<LCD_A0_BIT);} else 
{LCD_A0_PORT|=(1<<LCD_A0_BIT);}
  LCD_WR_PORT|=(1<<LCD_WR_BIT);
  LCD_DATEN_PORT=Bytewert;
  LCD_WR_PORT&=~(1<<LCD_WR_BIT);
  LCD_WR_PORT|=(1<<LCD_WR_BIT);              }

soweit so gut. (Funktioniert auch ;-)
Nun muß ich für einen 12X16 Font diese "Funktion" mehrmals aufrufen z.B. 
so:

  Lcd_Write(pgm_read_word(&x_font[Zeichen][0]),1,0);
  Lcd_Write(pgm_read_word(&x_font[Zeichen][2]),1,0);
  Lcd_Write(pgm_read_word(&x_font[Zeichen][4]),1,0);
  Lcd_Write(pgm_read_word(&x_font[Zeichen][6]),1,0);
  Lcd_Write(pgm_read_word(&x_font[Zeichen][8]),1,0);
  Lcd_Write(pgm_read_word(&x_font[Zeichen][10]),1,0);

nun ist mir aufgefallen, das pro Aufruf der Funktion meine Codesize um 
12 Bytes wächst, was genau der Größe dieser Funktion entspricht. (ich 
kann auch mehrmals den Code der Funktion ausführen...)

Ich dachte immer, wenn das ganze in Assembler umgesetzt wird, die 
Funktion z.B. an Adresse 0xirgentwas steht und wenn sie aus dem 
Hauptprogramm aufgerufen wird macht der Assembler dann sowas wie:

Lade Register mit Argument 1;
Lade Register mit Argument 2;
Speichere Rücksprungadresse auf dem Stack;
Jump zur Funktion an Adresse 0xirgentwas;

Mache ich da was falsch? Makefile Falsch eingestellt? ist das immer so 
und nicht zu ändern?

Danke ---=DIAN=---

von Uhu U. (uhu)


Lesenswert?

---=DIAN=--- wrote:
> soweit so gut. (Funktioniert auch ;-)
> Nun muß ich für einen 12X16 Font diese "Funktion" mehrmals aufrufen z.B.
> so:
>
>   Lcd_Write(pgm_read_word(&x_font[Zeichen][0]),1,0);
>   Lcd_Write(pgm_read_word(&x_font[Zeichen][2]),1,0);
>   Lcd_Write(pgm_read_word(&x_font[Zeichen][4]),1,0);
>   Lcd_Write(pgm_read_word(&x_font[Zeichen][6]),1,0);
>   Lcd_Write(pgm_read_word(&x_font[Zeichen][8]),1,0);
>   Lcd_Write(pgm_read_word(&x_font[Zeichen][10]),1,0);
>
> nun ist mir aufgefallen, das pro Aufruf der Funktion meine Codesize um
> 12 Bytes wächst, was genau der Größe dieser Funktion entspricht. (ich
> kann auch mehrmals den Code der Funktion ausführen...)

Deine Funktion Lcd_Write ist mit Sicherheit länger als 12 Byte.

> Ich dachte immer, wenn das ganze in Assembler umgesetzt wird, die
> Funktion z.B. an Adresse 0xirgentwas steht und wenn sie aus dem
> Hauptprogramm aufgerufen wird macht der Assembler dann sowas wie:
>
> Lade Register mit Argument 1;
> Lade Register mit Argument 2;
> Speichere Rücksprungadresse auf dem Stack;
> Jump zur Funktion an Adresse 0xirgentwas;

Jump geht nicht, es muß schon ein call sein, sonst findet er nicht mehr 
zurück.

Der Code wird nur dann physikalisch an Stelle des Aufrufes einkopiert, 
wenn  Lcd_Write keine Funktion, sondern ein Macro ist, oder wenn 
Lcd_Write eine inline-Funktion ist.

von Peter D. (peda)


Lesenswert?

---=DIAN=--- wrote:
>   Lcd_Write(pgm_read_word(&x_font[Zeichen][0]),1,0);
>   Lcd_Write(pgm_read_word(&x_font[Zeichen][2]),1,0);
>   Lcd_Write(pgm_read_word(&x_font[Zeichen][4]),1,0);
>   Lcd_Write(pgm_read_word(&x_font[Zeichen][6]),1,0);
>   Lcd_Write(pgm_read_word(&x_font[Zeichen][8]),1,0);
>   Lcd_Write(pgm_read_word(&x_font[Zeichen][10]),1,0);


2 Argumente sind konstant und eines steigt in 2-er Schritten, das 
schreit doch geradezu nach einer Schleife.


Peter

von holger (Gast)


Lesenswert?

>Lade Register mit Argument 1;
>Lade Register mit Argument 2;
>Speichere Rücksprungadresse auf dem Stack;
>Jump zur Funktion an Adresse 0xirgentwas;

  Lcd_Write(pgm_read_word(&x_font[Zeichen][0]),1,0);

12 Bytes pro Aufruf ist doch ok.
Nimm das mal auseinander:

pgm_read_word(&x_font[Zeichen][0])

Das ist schon ein Funktionsaufruf mit Parametern
auf ein immerhin zweidimensionales Array.

Das Ergebnis von pgm_read_word() wird widerum als Parameter
für Lcd_Write() benutzt, und dann kommen
noch zwei Parameter für die Position.

Mit 12 Bytes pro Aufruf von Lcd_Write()
bist du erstklassig bedient.

von ---=DIAN=--- (Gast)


Lesenswert?

Also erstmal Danke für die Antworten, dass mit ner Schleife ist mir 
schon klar, aber ich hätte gedacht das die OPTIMIERUNG daraus ne 
Schleife machen kann!? Sie kann ja auch nutzlosen Code entfernen (hab 
ich mal in nem Tread gelesen wo es um eigene Delays ging...)

Es ging mir ja um das Generelle. Wenn jetzt ___z.B.___ die Routine 
Lcd_Write mehr Code beinhaltet sagen wir mal 100 Bytes, und die 
Argumente nicht mit ner Formel berechnet werden können (z.B. 
1,9,3,7,11,17,13,27) was dann? Nen Array nehmen?

von ---=DIAN=--- (Gast)


Lesenswert?

Also für die Argumente meine ich....

von Uhu U. (uhu)


Lesenswert?

---=DIAN=--- wrote:
> Also erstmal Danke für die Antworten, dass mit ner Schleife ist mir
> schon klar, aber ich hätte gedacht das die OPTIMIERUNG daraus ne
> Schleife machen kann!? Sie kann ja auch nutzlosen Code entfernen (hab
> ich mal in nem Tread gelesen wo es um eigene Delays ging...)

Der Compiler ist zwar schlau, aber nicht allwissend. Um Konflikte zu 
vermeiden geht er bei Dingen, die er nicht versteht, davon aus, daß der 
Programmierer weiß, was er tut.

Wenn du mal richtig fit in C bist, würdest du dich bedanken, wenn er 
nach Gutdünken irgendwas aus deinem Code machte...

von Peter D. (peda)


Lesenswert?

---=DIAN=--- wrote:

> Argumente nicht mit ner Formel berechnet werden können (z.B.
> 1,9,3,7,11,17,13,27) was dann? Nen Array nehmen?

Ganz genau.

Eine Tabelle kostet ja nur den Tabelleneintrag, ist also sehr 
codeeffizient.


Peter

von holger (Gast)


Lesenswert?

>Lcd_Write mehr Code beinhaltet sagen wir mal 100 Bytes, und die
>Argumente nicht mit ner Formel berechnet werden können (z.B.
>1,9,3,7,11,17,13,27) was dann?

Nix was dann. Der Aufruf von Lcd_Write() kostet dich immer
nur noch 12 Byte. Egal wie groß Lcd_Write() ist.

von Oliver (Gast)


Lesenswert?

>Lcd_Write mehr Code beinhaltet sagen wir mal 100 Bytes, und die
>Argumente nicht mit ner Formel berechnet werden können (z.B.
>1,9,3,7,11,17,13,27) was dann?

Solange die Arrayindizes konstant sind, berechnte der Compiler die 
Elementadresse schon zur Compilezeit. Die Rechenzeit bleibt daher immer 
gleich.

>aber ich hätte gedacht das die OPTIMIERUNG daraus ne
>Schleife machen kann!?

Was wäre denn der Vorteil einer Schleife? Da muß dann für jeden 
Funktionsaufruf die Schleifenvariable erhöht und auf Abbruch geprüft 
werden, und die in Berechnung der Arrayelelementadressen, die in der 
alten Version schon zur Compilezeit durchgeführt wird, kommt auch noch 
dazu. Alles in allem deutlich länger.

Daher gibt es als Optimierung den umgekehrten Schritt: Schleifen mit zur 
Compilezeit bekannter Durchlaufzahl können vom Compiler in lineare 
Einzelaufrufe umgewandelt werden. ("loop unrolling").


Oliver

von Oliver (Gast)


Lesenswert?

Nachtrag: "loop unrolling" ist natürlich vor allem bei Optimierung auf 
Rechenzeit sinnvoll.

Oliver

von Karl H. (kbuchegg)


Lesenswert?

Oliver wrote:
> Was wäre denn der Vorteil einer Schleife? Da muß dann für jeden
> Funktionsaufruf die Schleifenvariable erhöht und auf Abbruch geprüft
> werden, und die in Berechnung der Arrayelelementadressen, die in der
> alten Version schon zur Compilezeit durchgeführt wird, kommt auch noch
> dazu. Alles in allem deutlich länger.

Was meinst du mit länger: Laufzeit oder Code?

Falls Code:
Das wage ich im konkreten Fall mal zu bezweifeln. In 12 Bytes für
einen Funktionsaufruf kann man leicht die ganze Schleifensteuerung
samt Adressberechnung unterbringen. Spätestens nach dem 2. Funktions-
aufruf dürfte die Schleifenvariante schon deutlich kürzer sein.

Falls Laufzeit:
Die entrollte Version wird sicherlich etwas schneller sein.
Ist im vorliegenden konkreten Fall ein inc und ein cmp.
Dazu eventuell noch ein push und ein pop um die
Schleifenvariable vor der Funktion zu schützen und das wars
mit der Schleifensteuerung. Die Adressberechnung, da sie
schön linear geht, wird der Compiler mit einem Pointer machen,
den er in der Schleife um immer einen konstanten Betrag erhöht.
Summa summarum ergibt sich natürlich ein Overhead und man muss
entscheiden was einem wichtiger ist: Laufzeit oder Codegrösse.
Allerdings: Heutige Compiler machen das Loop-unrolling ganz von
alleine, wenn man auf Laufzeit optimieren lässt.
Im Zweifelsfall schreibt man im Allgemeinen den Code so wie er
am klarsten ist und überlässt solche 'Low-Level' Optimierungen
dem Compiler. Wie sagte schon Donald E. Knuth
"Premature optimization is the root of all evil"
"Vorzeitige Optimierung ist die Wurzel allen Übels"

von Oliver (Gast)


Lesenswert?

>Was meinst du mit länger: Laufzeit oder Code?

Daher mein Nachtrag oben. Länger = Langsamer.

>Im Zweifelsfall schreibt man im Allgemeinen den Code so wie er
>am klarsten ist und überlässt solche 'Low-Level' Optimierungen
>dem Compiler.

Volle Zustimmung.

Oliver

von Falk B. (falk)


Lesenswert?

>dem Compiler. Wie sagte schon Donald E. Knuth
>"Premature optimization is the root of all evil"

Falsch, die Wahrheit ist viel schlimmer. ;-)

http://www.msxnet.org/humour/girls-are-evil.jpg

MFG
Falk

von ---=DIAN=--- (Gast)


Lesenswert?

Ok, wieder eineiges schlauer. Danke für eure Antworten...

---=DIAN=---

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.