Hallo
Ich habe eine Frage zum Aufruf einer Funktion (z.B. ATMega328,
programmiert in C):
was ist bezgl Speicherplatz und Rechengeschwindigkeit besser
- wenn eine Funktion in main() per for/while Schleife aufgerufen wird
oder
- wenn diese Funktion in main() nur 1mal aufgerufen wird und die
for/while Schleife
in der Funktion abgearbeitet wird
Danke!
Mikrooptimierungen? Lohnt meistens nicht.
Wenn du die Funktion "static" deklarierst, wird das Ergebnis vermutlich
komplett identisch sein. Wenn sie nicht "static" ist (und keine
link-time optimizations aktiv sind), dann könnte der Aufruf der Funktion
geringfügig mehr Overhead erzeugen. Aber wie geschrieben: das sind
Mikrooptimierungen. Die retten dir nur dann irgendwas, wenn irgendein
Timing "auf Kante genäht" ist, ansonsten geht sowas im Rauschen unter.
Strukturiere den Code so, dass du ihn auch in drei Jahren noch gut lesen
kannst, das ist viel wichtiger.
Der "Aufruf" erzeugt einen gewissen Overhead, d.h. besser die Schleife
in die Funktion packen. Je nach Optimierung kann dann der Aufruf inlined
und/oder die Schleife selbst entrollt werden. Kommt auf den Code an
Jeder Funktionsaufruf kostet ein paar Prozessor-Zyklen. Auch die lokalen
Variablen werden jedes Mal angelegt.
Also wäre die zweite Option etwas (geringfügig) günstiger.
Aber: Die Frage ist ob sich das in irgend einer Weise auf das System
auswirkt. Sinnvollerweise legt man Schleifen dort an wo sie logisch
gesehen hingehören um das Programm so auch lesbarer zu gestalten.
Alles Andere gehört in die Kategorie ›mehr oder weniger sinnloses
Optimieren‹.
Bezüglich Speicher ist es egal. Bezüglich Laufzeit ist die Schleife in
der Funktion schneller, die Zeit für den Funktionsaufruf fällt nur
einmal an.
Ob das für Deinen Fall relevant ist, müsste man extra betrachten.
Egonwalter M. schrieb:> Ich habe eine Frage zum Aufruf einer Funktion
auch wenn vielleicht schon alles gesagt ist: Funktionen macht man vor
allem, um Teiloperationen zu separieren/gruppieren. Wenn Du die Funktion
nur an einer Stelle brauchst, könntest Du sie sonst auch weglassen.
Darum gehört die Schleife dahin, wo die Schleifenbedingungen und äußeren
Abhängigkeiten thematisch hingehören. Oder anders herum: wo die Summe
der Parameter einfacher/weniger wird.
Wenn Du ein Beispiel bringst, wirst Du das verstehen.
A. S. schrieb:> Wenn Du die Funktion nur an einer Stelle brauchst, könntest Du sie sonst> auch weglassen.
Wie du eben schreibst: zum Separieren/Gruppieren kann das trotzdem
sinnvoll sein. Wenn man einen Sack voller "static"-Funktionen jeweils
nur an einer Stelle aufruft, dann packt der Compiler sie ohne mit der
Wimper zu zucken alle inline in den Aufrufer. Das Ergebnis ist also das
gleiche, wie wenn man gleich alles in den Aufrufer geschrieben hätte –
außer dass die Strukturierung in abgetrennte Funktionen in aller Regel
die Lesbarkeit verbessert. Das allein kann also Grund genug sein, es
aufzuteilen.
Der Compiler optimiert den Code besser als man denkt. Insofern sollte
man sich eher darauf konzentrieren, dass der Quelltext für den Menschen
gut lesbar ist.
Abgesehen davon sind gerade beim AVR Funktionsaufrufe deutlich
"billiger", als bei vielen anderen Mikrocontrollern.
Ich verpacke mittlerweile sogar Zugriffe auf einzelne I/O Pins in
Funktionen, bloß um ihnen sprechende Namen zu geben:
1
voidset_power_led_on(){
2
PORTB|=1;
3
}
4
5
voidset_power_led_off(){
6
PORTB&=~1;
7
}
8
9
boolstart_button_pressed(){
10
return(PINB&2);
11
}
Selbst so etwas wird vom Compiler auf ein Minimum (nämlich einen
einzigen Assembler Befehl) reduziert, wenn man es mit einem konstanten
Parameter aufruft:
Schreibe den Code so, dass er für einen Menschen gut lesbar ist.
Den schlimmsten Code erzeugen nämlich Programmierer, die glauben
schlauer als der Compiler zu sein ;)
Norbert schrieb:> Jeder Funktionsaufruf kostet ein paar Prozessor-Zyklen. Auch die lokalen> Variablen werden jedes Mal angelegt.
Du vergisst, dass der Optimizer den Code umstrukturiert. Dein Satz gilt
so generell nur, wenn man den Compiler mit Option -O0 aufruft.
Stefan ⛄ F. schrieb:> Wir hatten hier mal einen Assembler Spezi, der meinte, er könne es> besser als der C Compiler. Die Wette hatte er verloren.
Naja, und wir hatten Projektansätze wie etwa einen Spektrum-Analyzer für
den Audio-Bereich oder einen Westminster-Gong, die aber niemals
benutzbare Realität wurden, bis sich kompetente Assemblerprogrammierer
ihrer angenommen haben...
Bitte auch das bedenken...
Und weiters bitte ich zu bedenken: fast alles, was unfähige
C-Programmierer im Bereich Signalanlyse im Laufe der Jahre auf dem AVR8
gebacken bekommen haben, basiert wiederum auf den FFT-Routinen von Elm
Chan, der die einerseits auch wieder in Assembler verfaßt hat,
andererseits aber wohl eindeutig C bevorzugt, wenn möglich.
Sprich: es muß wohl doch was dran sein, dass man mit Asm deutlich
effizienteren Code erzeugen kann als es ein C-Compiler könnte...
Das kannst du nicht wegdiskutieren.
Mal ganz davon abgesehen: man braucht doch bloß in den Code der Compiler
selber zu schauen: Assembler ohne Ende. Das wird nur vor den volldummen
C-only-Schranzen versteckt, aber es ist DA.
c-hater schrieb:> Mal ganz davon abgesehen: man braucht doch bloß in den Code der Compiler> selber zu schauen
Gemeint ist natürlich nicht der Code der Compiler selber, sondern der
der Runtime für das Target. Das noch zu Klarstellung.
Stefan ⛄ F. schrieb:> Norbert schrieb:>> Jeder Funktionsaufruf kostet ein paar Prozessor-Zyklen. Auch die lokalen>> Variablen werden jedes Mal angelegt.>> Du vergisst, dass der Optimizer den Code umstrukturiert. Dein Satz gilt> so generell nur, wenn man den Compiler mit Option -O0 aufruft.
Das macht er? Macht er das auch wenn die Funktion an verschiedenen
Stellen (Sourcecode-Dateien) aufgerufen wird und selbst ebenso in einer
separaten Datei (oder vielleicht in einer Library) liegt?
Aber ich weiß worauf du hinaus willst… ;-)
Stefan ⛄ F. schrieb:> c-hater schrieb:>> egal was>> Tschüss, ich bin raus
Du bist vor allem auch eins: Ein Zitatfälscher. "Egal was" schrieb ich
an keiner Stelle.
Mal rein "gefühlt": Wenn Du in der main() den Aufruf auf eine Funktion
mit einer Endlos-Loop findest, und dieses Programm erweitern sollst...
...ok, ich mach das nicht mehr professionell, aber ich würde mich
verarscht fühlen.
c-hater schrieb:> Stefan ⛄ F. schrieb:>>> Wir hatten hier mal einen Assembler Spezi, der meinte, er könne es>> besser als der C Compiler. Die Wette hatte er verloren.>> Naja, und wir hatten Projektansätze wie etwa einen Spektrum-Analyzer für> den Audio-Bereich oder einen Westminster-Gong, die aber niemals> benutzbare Realität wurden, bis sich kompetente Assemblerprogrammierer> ihrer angenommen haben...> Bitte auch das bedenken...> Und weiters bitte ich zu bedenken: fast alles, was unfähige> C-Programmierer im Bereich Signalanlyse im Laufe der Jahre auf dem AVR8> gebacken bekommen haben, basiert wiederum auf den FFT-Routinen von Elm> Chan, der die einerseits auch wieder in Assembler verfaßt hat,> andererseits aber wohl eindeutig C bevorzugt, wenn möglich.> Sprich: es muß wohl doch was dran sein, dass man mit Asm deutlich> effizienteren Code erzeugen kann als es ein C-Compiler könnte...> Das kannst du nicht wegdiskutieren.> Mal ganz davon abgesehen: man braucht doch bloß in den Code der Compiler> selber zu schauen: Assembler ohne Ende. Das wird nur vor den volldummen> C-only-Schranzen versteckt, aber es ist DA.
Ein reflektierter und abwägender Beitrag von c-hater. Lesen war spannend
-- ich hab die ganze Zeit auf den Holzhammer gewartet und er kam nicht!
Und bei den volldummen Schranzen ganz zum Schluss war es dann irgendwie
schon zu spät ...
LG, Sebastian
Jörg W. schrieb:> Norbert schrieb:>> Macht er das auch>> mit -flto ggf. schon
OK. Also, ich schreibe eine Funktion welche einen Sack voller lokaler
Variablen anlegt, verarbeitet und einen Return Wert generiert.
Die packe ich in eine Library.
Und der Compiler zupfelt mir das später auseinander, arrangiert alles
neu und packt es wo auch immer hin?
Ähm, ich vermute eher nicht.
Aber du schriebst ja auch: Link-Time Optimierung und ggf
Norbert schrieb:> Jeder Funktionsaufruf kostet ein paar Prozessor-Zyklen. Auch die lokalen> Variablen werden jedes Mal angelegt.
Das anlegen der Variablen kostet aber keine Zeit.
Dirk B. schrieb:> Das anlegen der Variablen kostet aber keine Zeit.
Das Anlegen der ersten Variable kostet Zeit, wenn dafür Platz auf dem
Stack reserviert wird. Bei Prozessoren, deren Hersteller Rechnungen mit
dem Stack-Pointer völlig verpennt haben (AVR), kann das auch weitere
Variablen betreffen.
Jede Variable kostet Zeit, wenn ein zusätzliches Register gesichert
werden muss.
Dirk B. schrieb:> Norbert schrieb:>> Jeder Funktionsaufruf kostet ein paar Prozessor-Zyklen. Auch die lokalen>> Variablen werden jedes Mal angelegt.>> Das anlegen der Variablen kostet aber keine Zeit.
Auf'm Stack muss der (frame)Pointer angepasst werden. OK, nur wenige
Zyklen. Lokale Variablen müssen jedes mal initialisiert werden. Auch nur
wenige Zyklen. Selbst ein lokaler alloca() ist überschaubar.
Was aber wenn ich mit malloc() einen großen Arbeitbereich brauche?
Einmal malloc(), 1.000.000 mal Schleife und arbeiten, einmal free()
oder
1.000.000 mal eine Funktion aufrufen mit jeweils:
malloc(), arbeiten, free()
Es kommt immer auf den jeweiligen Fall an.
Jörg W. schrieb:> Mikrooptimierungen? Lohnt meistens nicht.>> Strukturiere den Code so, dass du ihn auch in drei Jahren noch gut lesen> kannst, das ist viel wichtiger.
Hallo an Alle
Vielen Dank für Eure Hilfe!
Jörg W.s Tipp +1!
Norbert schrieb:> Und der Compiler zupfelt mir das später auseinander, arrangiert alles> neu und packt es wo auch immer hin?
So ist es. Auch, wenn die Dinger "link-time optimizations" heißen, wird
in Wirklichkeit nach dem Linken nochmal der Compiler über Teile des
Codes geschickt. Daher funktioniert es auch nur, wenn man das -flto
sowohl beim Compilieren als auch beim Linken angibt, denn es wird
(zumindest habe ich das mal so verstanden) in den Objektdateien dafür
noch irgendein Zwischencode hinterlegt.
Deine fiktive Bibliothek müsste also ebenfalls ihre Module mit -flto
compiliert bekommen haben.
> und ggf
Ja, natürlich, Optimierungen sind immer Abwägungen.
Norbert schrieb:> Und der Compiler zupfelt mir das später auseinander, arrangiert alles> neu und packt es wo auch immer hin?
Mal ein Beispiel.
Wir haben die Datei a.c:
1
#include<avr/io.h>
2
3
voidtoggle_pin(void){
4
PINC=1;
5
}
Die compilieren wir und packen sie in eine Library:
1
$ avr-gcc -Os -mmcu=atmega328 -flto -c a.c
2
$ avr-ar rv a.a a.o
3
avr-ar: creating a.a
4
a - a.o
5
avr-ar: a.o: plugin needed to handle lto object
Oh! Das mit -flto compilierte Objektmodul hat irgendwas, was eine
Sonderbehandlung braucht. ;-) (Wusste ich bis jetzt auch noch nicht.)
Jörg W. schrieb:> Wie du eben schreibst: zum Separieren/Gruppieren kann das trotzdem> sinnvoll sein.
So war das "sonst" gemeint: wenn es nicht um dieses Vorteils wäre.
Mein oben genanntes Beispiel:
> void set_power_led(bool on) {> if (on) PORTB |= 1;> else PORTB &= ~1;> }
wird ebenfalls auf einen einzigen Assembler Befehl reduziert, selbst
wenn es in einer anderen *.c Datei steht, als wo es benutzt wird.
Aber nur, wenn -flto benutzt wird und die Optimierungsstufe mindestens
auf -O2 oder -Os gestellt ist. Mit -O1 bleibt es ein Funktionsaufruf.
Stefan ⛄ F. schrieb:> Ich verpacke mittlerweile sogar Zugriffe auf einzelne I/O Pins in> Funktionen, bloß um ihnen sprechende Namen zu geben:> void set_power_led_on() {> PORTB |= 1;> }
Ich (als Laie) verwende Defines dafür.
Auch der Codegenerator von Microchip, wirft mir für diesen Zweck welche
aus. Nur ha ich nicht ganz (eher gar nicht:) verstanden, warum das in
einem do-while Block gepackt wird?-{
#define LED_TEST_SetHigh() do { LATAbits.LATA0 = 1; } while(0)
#define LED_TEST_Toggle() do { LATAbits.LATA0 = ~LATAbits.LATA0; }
while(0)
Ich vermute mal, das hat was mit den darin enthaltenen, weiteren Defines
zu tun.
Teo D. schrieb:> Nur ha ich nicht ganz (eher gar nicht:) verstanden, warum das in> einem do-while Block gepackt wird?-{
Die defines sind unter gewissen Umständen problematisch. Was du da
siehst, ist ein Workaround, der diese Probleme vermeiden soll.
Schau mal
https://stackoverflow.com/questions/14041453/why-are-preprocessor-macros-evil-and-what-are-the-alternatives
Den Absatz "Macro expansions can have strange side effects.". In der
Diskussion werden noch einige weitere potentielle Probleme mit dem
Markos genannt.
Ich fand Makros früher mal super toll für HW Zugriffe, bin aber auch
wieder davon weg gekommen, nach ich mit damit selbst ein paar mal ins
Knie geschossen hatte und auch oft mit unverständlichen Fehlermeldungen
vom Compiler konfrontiert wurde.
Die vielen Beispiele mit aus heutiger Sicht unnötigen Makros stammen aus
Zeiten, als der Optimizer im Compiler noch nicht so gut war.
Stefan ⛄ F. schrieb:> Die vielen Beispiele mit aus heutiger Sicht unnötigen Makros stammen aus> Zeiten, als der Optimizer im Compiler noch nicht so gut war.
Explizites Inlining steht im Standard erst mit C99 zur Verfügung. Davor
gab es das in C++ und als Erweiterung im GNU Compiler.
Teo D. schrieb:> Nur ha ich nicht ganz (eher gar nicht:) verstanden, warum das in> einem do-while Block gepackt wird?-{>> #define LED_TEST_SetHigh() do { LATAbits.LATA0 = 1; } while(0)> #define LED_TEST_Toggle() do { LATAbits.LATA0 = ~LATAbits.LATA0; }> while(0)>> Ich vermute mal, das hat was mit den darin enthaltenen, weiteren Defines> zu tun.
Nein, es hat eher im Gegenteil damit zu tun, wo du die Defines
verwendest, weil es da recht fiese versteckte Fallstricke geben kann.
Allerdings ist es in diesem speziellen Fall eigentlich unnötig.
Interessanter wäre es, wenn da z.B. nicht eine, sondern zwei Zuweisungen
wären.
dann landet nur die erste der beiden Zuweisungen in dem if, und die
andere wird immer ausgeführt, unabhängig von irgendwas. Das do/while
sorgt dafür, dass beide im if landen. Mit Funktionen statt Makros sind
solche work-arounds nicht nötig.
Norbert schrieb:> Was aber wenn ich mit malloc() einen großen Arbeitbereich brauche?> Einmal malloc(), 1.000.000 mal Schleife und arbeiten, einmal free()> oder> 1.000.000 mal eine Funktion aufrufen mit jeweils:> malloc(), arbeiten, free()>> Es kommt immer auf den jeweiligen Fall an.
Es gibt keinen AVR, auf dem das erste Szenario überhaupt funktionieren
würde, da du die 1.000.000 allokierten Objekte gar nicht gleichzeitig in
den Speicher bekämst. Das heißt, es bleibt in so einem Fall nur die
zweite Option.
(prx) A. K. schrieb:> Stefan ⛄ F. schrieb:>> Die vielen Beispiele mit aus heutiger Sicht unnötigen Makros stammen aus>> Zeiten, als der Optimizer im Compiler noch nicht so gut war.>> Explizites Inlining steht im Standard erst mit C99 zur Verfügung. Davor> gab es das in C++ und als Erweiterung im GNU Compiler.
Das Schlüsselwort "inline" wird nicht dazu verwendet, um dem Compiler zu
sagen, dass er es inlinen soll. Die Entscheidung, ob er das tut, trifft
er ganz unabhängig davon. "inline" sorgt nur dafür, dass die one
definition rule verletzt werden darf.
Bei gcc gibt's noch __attribute__((always_inline)), mit dem man das
inlining tatsächlich forcieren kann. Wenn das nicht möglich ist, bricht
er dann auch mit Fehler ab.
Rolf M. schrieb:> Wenn du jetzt so etwas schreibst:if (irgendwas)> LED_SetBothLEDs();> dann landet nur die erste der beiden Zuweisungen in dem if, und die> andere wird immer ausgeführt, unabhängig von irgendwas. Das do/while> sorgt dafür, dass beide im if landen. Mit Funktionen statt Makros sind> solche work-arounds nicht nötig.
So einen Fall hatte ich noch nicht. Manchmal hat man auch "Glück". :-}
Sollte aber bei anzeige mit Macro expansion auffallen.... Wenn einem
die Fehlermeldungen nicht in die irre führen.
Stefan ⛄ F. schrieb:> Den Absatz "Macro expansions can have strange side effects.". In der> Diskussion werden noch einige weitere potentielle Probleme mit dem> Markos genannt.
Das werde ich mir mal in einer ruhigen Stunde ansehen.
Ich werde aber dabei bleiben, den der Pin-Manager (Codegenerator) ist
einfach zu bequem und bei meinen "popeligen" µC Projecten, ist das Ganze
ja auch noch recht übersichtlich.
Danke
Stefan ⛄ F. schrieb:> Also seit 23 Jahren!
In der Programmentwicklung bei Mikrocontrollern ist das quasi erst seit
letzter Woche. Dieses Business folgt Sprachentwicklungen oft nur mit
sehr langer Verzögerung.
Jene Compiler, die speziell für dieses Genre entwickelt wurden, sind
selbst oft im Kern uralt und nicht annähernd so optimierungsfreudig wie
GCC.
Das hat in der Praxis nicht nur Nachteile. Schon viele hat das korrekte
aber eben sehr flexible Verhältnis des GCC zum Quellcode aus der Kurve
geworfen. Umordnung und weglassen von Code kommt in der Branche nicht
immer gut an.
(prx) A. K. schrieb:> Schon viele hat das korrekte> aber eben sehr flexible Verhältnis des GCC zum Quellcode aus der Kurve> geworfen. Umordnung und weglassen von Code kommt in der Branche nicht> immer gut an.
Nu ja, die Diskussion ist so alt wie „ohne Optimierung läufts, mit
nicht“.
Oliver
(prx) A. K. schrieb:> Schon viele hat das korrekte aber eben sehr flexible Verhältnis des GCC> zum Quellcode aus der Kurve geworfen.
IAR optimiert ähnlich rabiat wie GCC und macht zumindest C99 schon sehr
lange – inklusive vollständiger Standardbibliothek.
Teo D. schrieb:> arum das in einem do-while Block gepackt wird?
Das ist ein elegantes Mittel, um das ;- oder }-Problem zu lösen.
Das define sieht aus wie ein Funktionsaufruf. Dahinter sollte im Code
ein ; stehen.
enthält das #define ein ; oder } am Ende, dann bekommst Du durch Dein ;
eine Warnung. Und Dinge wie
1
...
2
if(y)
3
if(x<8)SetLEDs(x);
4
elseSetLEDs(0);
gehen schief ohne dass es auffällt (OK, würde man sagen, selber Schuld
;-)
Enthält es keines, z.B.:
1
#define SetLEDs(mask) Portx=mask
dann kann der Compiler nicht warnen, wenn Du was abgefahrenes machst,
z.B.:
1
SetLEDs(0x18)+4;
2
SetLEDs(mask)=0;
3
SetLEDs(0x18)&1;
4
SetLEDs(=0x18);
5
void*p;p=&SetLEDs(0);/* komisch, ich muss für den Funktionspointer ein Argument angeben. Vermutlich ein Compiler-Bug, aber es compiliert so!!! */
A. S. schrieb:> Teo D. schrieb:>> arum das in einem do-while Block gepackt wird?>> Das ist ein elegantes Mittel, um das ;- oder }-Problem zu lösen.
Oh.... Da fällt's einem doch wie Schuppen..... :DDD
Danke
c-hater schrieb:> Und weiters bitte ich zu bedenken: fast alles, was unfähige> C-Programmierer im Bereich Signalanlyse im Laufe der Jahre auf dem AVR8> gebacken bekommen haben, basiert wiederum auf den FFT-Routinen von Elm> Chan, der die einerseits auch wieder in Assembler verfaßt hat,> andererseits aber wohl eindeutig C bevorzugt, wenn möglich.> Sprich: es muß wohl doch was dran sein, dass man mit Asm deutlich> effizienteren Code erzeugen kann als es ein C-Compiler könnte..
Das sind ganz spezielle Funktionen bei denen bestimmte Algorithmen
möglichst effizient implementiert wurden. Was jedoch bei 99% der
Anwendungen mittlerweile völliger Blödsinn ist, da blockiert dann vorher
der Speicherdurchsatz usw.
Ein moderner C Compiler würde vermutlich ähnliche Aufrufe produzieren da
diesem die meisten Algorithmen und die dazugehörigen Implementierungen
je nach Architektur bekannt sein sollten.
Du versucht damit gerade Spezialfälle auf allgemeine Anwendungen zu
übertragen. Und das kannst du bei heutiger Software vergessen.
Stefan ⛄ F. schrieb:> Tschüss, ich bin raus
Ein kurzer Moment der Hoffnung!
Stefan ⛄ F. schrieb:> Mein oben genanntes Beispiel:
und wieder vorbei /:
Ponton 1872 schrieb:> Du versucht damit gerade Spezialfälle auf allgemeine Anwendungen zu> übertragen. Und das kannst du bei heutiger Software vergessen.
Spezialfälle wird es immer geben. Die meisten mir bekannten crypto
mining Programme nutzen Assembler (natürlich nicht das ganze Programm!).
BG
Stored B. schrieb:> Die meisten mir bekannten crypto mining Programme nutzen Assembler
Und die umsatzmäßig stärksten Miner nutzen eigene Hardware.
Sollte es also jetzt das Fazit sein, dass man alle Probleme so weit
zusammen optimiert, bis man für jedes eine eigene Hardware hat? ;-)
Unsinnige Vergleiche … Das Thema war so etwas simples wie ein
Funktionsaufruf.
Jörg W. schrieb:> Unsinnige Vergleiche … Das Thema war so etwas simples wie ein> Funktionsaufruf.
Das ist ja auch das Problem. Solche Themen werden in den meisten C
Büchern ausführlichst durchgekaut..
Ist diese Interesse nicht da, gibt es immer noch google oder den asm
Output des Compilers.
Jörg W. schrieb:> für jedes eine eigene Hardware hat?
Je nach Anforderung. Roboter Systeme, SPS, Hausautomatisierung bauen
alle auf eigene Hardware auf. Teils sogar selbst entworfene Prozessoren.
Aber ist ja auch wurscht, jedes Projekt hat eigene Anforderungen.
BG