Forum: Mikrocontroller und Digitale Elektronik Code Optimierung (Geschwindigkeit) AVR, C


von Verwirrter Anfänger (Gast)


Angehängte Dateien:

Lesenswert?

Hi,
ich versuche gerade ein Programm so weit zu optimieren, dass es 
zumindest auch mit 6.4 MHz statt 8MHz läuft, idealerweise auch mit 1 
MHz.

Zur Zeit läuft das Programm mit 6.4 MHz, wenn ich als Optimierung -O2 
benutze, allerdings gehen dann ein paar andere Sachen kaputt, 
insbesondere geht der Controller nicht mehr in den power down mode (oder 
wacht zumindest sofort wieder auf).

Mit -O0, -O1 und -Os läuft das Programm nur bei 8 MHz.

Ich vermute, dass das Problem darin liegt, dass ich per Software SPI ein 
RFM12 Funkmodul anspreche, und dabei nicht schnell genug die Daten 
rausschicke für 49 kbps.

Jetzt gibt es für mich 2 Lösungsansätze:
- Das Programm so fixen, dass es auch mit -O2 richtig funktioniert
- Das Programm so optimieren, dass es auch mit -O1 schnell genug ist

Allerdings komm ich bei beiden Problemen nicht weiter. Ich hab schonmal 
probehalber die Funktionen "rfm12Cmd" und "rfm12_sendByte" als inline 
deklariert, aber das hat nicht ausgereicht.

Falls ihr vorschläge zu einem der Probleme habt würde ich mich über 
Antworten sehr freuen ;)

Ciao,
VA

von Guru (Gast)


Lesenswert?

Ich meine, so geht das nicht, das Du lediglich sagst, das Programm soll 
schneller sein. Das ist zu allgemein, um Dir helfen zu können.

Du musst erstmal feststellen welcher Programmteil langsamer läuft als 
Du es Dir wünschst. Dann gucke Dir den Teil an. Evtl. musst Du einen 
anderen Algorithmus nehmen. Zu konkreten Alternativen kannst Du uns dann 
fragen.

Abgesehen davon, ist ein Programm, das mit -O2 nicht läuft, obwohl es 
ohne Optimierung läuft korrekturbedürftig. Es dürfte allenfalls 
schneller laufen, sich abers sonst nicht anders verhalten. Das solltest 
Du als erstes tun, ehe Du grundsätzlich an Geschwindigkeitsoptimierung 
denkst.

von Guru (Gast)


Lesenswert?

Ohne das ich das jetzt im einzelnen nachgeprüft habe, wird die maximale 
Tatktfrequenz der SPI-Schnittstelle von der Systemtaktfrequenz abhängen. 
Das Datenblatt sollte Dir Auskunft geben, mit welchem Takt, welche 
SPI-Frequenz möglich ist.

von Wanzenprofi (Gast)


Lesenswert?

Verwirrter Anfänger schrieb:
> Mit -O0, -O1 und -Os läuft das Programm nur bei 8 MHz.

Ich würde mich erstmal nicht drauf verlassen, dass -O2 durchgängig 
schnelleren Code als -Os erzeugt. Auf dem AVR sind die meisten Befehle 
in 1 oder 2 Taktzyklen abgearbeitet, sodass auf Grösse optimierter Code 
schneller als -O2 sein kann. YMMV.

Schau dir erstmal das ASM listing an, vor allem der Routinen, die du im 
Verdacht hast. Wieso sollte -O2 etwas "kaputt optimieren"? Auf dieses 
Problem bin ich als Hobbyist bisher nicht gestossen (gut, ich optimiere 
eigentlich immer auf Grösse).

Versuch auch mal mit dem alten Compiler zu übersetzen (avr-gcc-select) 
und vergleich die Ergebnisse.

von Verwirrter Anfänger (Gast)


Lesenswert?

Guru schrieb:
> Ich meine, so geht das nicht, das Du lediglich sagst, das Programm soll
> schneller sein. Das ist zu allgemein, um Dir helfen zu können.
>
> Du musst erstmal feststellen welcher Programmteil langsamer läuft als
> Du es Dir wünschst. Dann gucke Dir den Teil an. Evtl. musst Du einen
> anderen Algorithmus nehmen. Zu konkreten Alternativen kannst Du uns dann
> fragen.
Vielleicht hatte ich das nicht richtig ausgedrückt, es geht mir nicht 
rein um die Optimierung auf Geschwindigkeit. Ich hab folgende Situation:
Ein Programm läuft immer bei 8MHz, bei 6.4MHz läuft es nur wenn ich -O2 
nutze. Allerdings geht dann der Schlaf modus kaputt.
Hieraus folgere ich:
Bei den Optimierung 0,1 und s ist das Programm zu langsam. Bei der 
Optimierung 2 werden irgendwelche Abfragen wegoptimert (wahrscheinlich 
busy waits) die dazu führen, dass der µC nicht mehr schlafen geht.

Als Kandidat für die Optimierung hab ich den SPI Transfer im Verdacht, 
da der die 49 kbps des Funkmoduls zufriedenstellen muss. Das sind vor 
allem die Funktionen "rfm12Cmd" und "rfm12_sendByte".

In meinem ganzen Programm gibt es eigentlich keinen aufwendigen 
Algorithmus, also zumindest nichts was man von O(n) zu O(log n) 
optimieren könnte, eigentlich sind es nur Protokollimplementierungen.

> Abgesehen davon, ist ein Programm, das mit -O2 nicht läuft, obwohl es
> ohne Optimierung läuft korrekturbedürftig. Es dürfte allenfalls
> schneller laufen, sich abers sonst nicht anders verhalten. Das solltest
> Du als erstes tun, ehe Du grundsätzlich an Geschwindigkeitsoptimierung
> denkst.
Ich gehe davon aus, das hier die Reihenfolge von Operationen 
umgefwürfelt wird, oder while Schleifen wegoptimiert werden. Allerdings 
bin ich mir noch nicht sicher, inwiefern dass den Schlafmodus 
beieinflussen sollte.
Aber hier fehlt mir halt die Erfahrung, wie man sowas richtig 
programmiert, welche Variante währe zum Beispiel richtig:
1
  while(RFM12_INT_PIN&(1 << RFM12_INT_BIT));
oder
1
  while(RFM12_INT_PIN&(1 << RFM12_INT_BIT)) __asm volatile ("nop"::);
oder vielleicht
1
  while((volatile) (RFM12_INT_PIN&(1 << RFM12_INT_BIT)));


Guru schrieb:
> Ohne das ich das jetzt im einzelnen nachgeprüft habe, wird die maximale
> Tatktfrequenz der SPI-Schnittstelle von der Systemtaktfrequenz abhängen.
> Das Datenblatt sollte Dir Auskunft geben, mit welchem Takt, welche
> SPI-Frequenz möglich ist.

Verwirrter Anfänger schrieb:
> per Software SPI

Ich denke nicht, dass die maximale Frequenz das Problem ist, sondern die 
minimale. ich nehme an, dass meine SPI implementierung die Daten nicht 
schnell genug rausschickt um den Sender zu befüllen.
Insbesondere nehme ich an, dass der Aufruf der Unterfunktionen zu lange 
dauert und zu aufwendig ist, und dass diese bei -O2 ge-inlined werden.

Wanzenprofi schrieb:
> Ich würde mich erstmal nicht drauf verlassen, dass -O2 durchgängig
> schnelleren Code als -Os erzeugt. Auf dem AVR sind die meisten Befehle
> in 1 oder 2 Taktzyklen abgearbeitet, sodass auf Grösse optimierter Code
> schneller als -O2 sein kann. YMMV.

Das Problem ist halt, dass -O2 funktioniert, -Os aber nicht. Ich denke 
dass das daran liegt, dass bei -Os eher Unterfunktionen als Funktionen 
belassen werden, während die bei -O2 eher mal als inline gesetzt werden, 
auch wenn der Code größer wird.

Wanzenprofi schrieb:
> Schau dir erstmal das ASM listing an, vor allem der Routinen, die du im
> Verdacht hast. Wieso sollte -O2 etwas "kaputt optimieren"? Auf dieses
> Problem bin ich als Hobbyist bisher nicht gestossen (gut, ich optimiere
> eigentlich immer auf Grösse).
Ich tippe mal, dass da Warteschleifen wegoptimiert werden und die 
Reihenfolge von Befehlen verändert wird.
Aber ich werd mir mal das ASM listing angucken, danke für den Tipp

Wanzenprofi schrieb:
> Versuch auch mal mit dem alten Compiler zu übersetzen (avr-gcc-select)
> und vergleich die Ergebnisse.

Danke, dass werd ich gleich probieren.

von Guru (Gast)


Lesenswert?

Aber irgendwas stimmt doch da nicht, bei Deiner Darstellung.

>Zur Zeit läuft das Programm mit 6.4 MHz, wenn ich als Optimierung -O2
>benutze, allerdings gehen dann ein paar andere Sachen kaputt

Was heisst denn, es "gingen ein paar Sachen kaputt". Ich bin, Deinem 
ersten Post zufolge, davon ausgegangen, dass das Programm eben nicht 
läuft. Bitte drücke Dich klarer aus.

>Als Kandidat für die Optimierung hab ich den SPI Transfer im Verdacht,
Verdacht? Prüf' es nach und dann sieh' weiter.

Was hier einfach zweifelhaft ist, ist das ein Programm 
geschwindigkeitsmäßig durch die compilereigene Optimierung so hingebogen 
werden soll, dass es schnell genug ist um zu funktionieren. Das kann ein 
Profi in Ausnahmefällen sicherlich mal machen, wenn es anders garnicht 
mehr geht. Aber wenn das nötig ist, wenn ein Anfänger was schreibt, dann 
ist irgendwas grundsätzlich faul, denke ich. Das Programm muss so 
parametriesierbar sein, das es (in den Grenzen des Grundsätzlichen) mit 
jeder Frequenz geht.

>Ich denke nicht, dass die maximale Frequenz das Problem ist, sondern die
>minimale.
Von was für einer minimalen oder maximalen Frequenz redest Du?

Ich lese hier

>Ich tippe mal,...
>... Verdacht ...
>Ich denke ...
>Ich denke nicht, ...
>Ich nehme an...

Nimm nicht nur an, sondern prüfe nach. Denke nicht nur, sondern ermittle 
Fakten. Nicht tippen, das ist hier kein Glücksspiel.

Darauf will ich Dich nur mal aufmerksam machen. Ich meine es nicht böse, 
will Dich nicht runtermachen oder so. Ich meine das ganz freundlich, 
aber die Wahrheit klingt nunmal nicht immer angenehm.

Bisher kommt mir Dein Post vor wie eines der oft hier anzutreffenden, wo 
jemand sich nicht die Mühe macht oder vielleicht auch nicht in der Lage 
ist, gedanklich, d.h. sprachlich und/oder bildlich stringent vorzugehen.
Erwartest Du das sich hier jemand mit diesen Andeutungen, Vermutungen 
und Annahmen Deinen Code anschaut um ein Problem zu finden, das 
überhaupt nicht definiert ist.

Deswegen der Tip. Finde erstmal heraus wo genau der Flaschenhals liegt. 
Finde erstmal heraus, was bei der Optimierung passiert, was Deinen Code 
unbrauchbar macht. Das machen wir hier im allgemeinen nicht für Dich. 
(Es gibt allerdings hier Leute die sich auch diese Mühe machen).

von Guru (Gast)


Lesenswert?

Und was diese Frage hier betriff:

>Aber hier fehlt mir halt die Erfahrung, wie man sowas richtig
>programmiert, welche Variante währe zum Beispiel richtig:
1
  while(RFM12_INT_PIN&(1 << RFM12_INT_BIT));
>oder
1
  while(RFM12_INT_PIN&(1 << RFM12_INT_BIT)) __asm volatile ("nop"::);
>oder vielleicht
1
  while((volatile) (RFM12_INT_PIN&(1 << RFM12_INT_BIT)));

so jag' das doch einfach mal durch den Compiler und schau' Dir das 
Assemblerlisting an. Dann siehst Du was passiert.

von Uwe (de0508)


Lesenswert?

Hallo,

ich arbeite mir diesen CFlags:
1
CFLAGS += -Os
2
CFLAGS += -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums
3
CFLAGS += -Wall -Wstrict-prototypes
4
CFLAGS += -fno-inline-small-functions
5
CFLAGS += -fno-split-wide-types
6
CFLAGS += -fno-tree-scev-cprop
7
CFLAGS += -fno-move-loop-invariants
8
CFLAGS += -ffunction-sections -fdata-sections -Wl,--gc-sections
9
CFLAGS += -Wl,--relax

Das führt bei mir zu kleinem Code,

wenn dein µP GPIOR0 - GPIOR2 hat, sollte man in GPIOR0 Bitfelder anlegen 
und in GPIOR1 und GPIOR2 char Variable ablegen, die häufig gebraucht 
werden.

So habe ich gerade ein Programm zusätzlich von 4340 Bytes auf 4254 Bytes 
verkleinert.

von Verwirrter Anfänger (Gast)


Lesenswert?

Guru schrieb:
> Aber irgendwas stimmt doch da nicht, bei Deiner Darstellung.
>
>>Zur Zeit läuft das Programm mit 6.4 MHz, wenn ich als Optimierung -O2
>>benutze, allerdings gehen dann ein paar andere Sachen kaputt
>
> Was heisst denn, es "gingen ein paar Sachen kaputt". Ich bin, Deinem
> ersten Post zufolge, davon ausgegangen, dass das Programm eben nicht
> läuft. Bitte drücke Dich klarer aus.
Grundsätzlich geht es um einen kleinen Funksender mit einem ATtiny 45. 
Bei 6.4 MHz und -O2 sendet der Sensor, aber geht nicht mehr schlafen 
nach dem Senden, er sendet also durchgehend.
Bei 8 MHz und O0, O1 und Os sendet er, geht dann für 150 Sekunden 
schlafen und sendet dann wieder.
Bei 6.4 MHz und -O0, -O1, -Os sendet er gar nicht, und dem 
Stromverbrauch nach zu urteilen geht er auch nicht schlafen.

>
>>Als Kandidat für die Optimierung hab ich den SPI Transfer im Verdacht,
> Verdacht? Prüf' es nach und dann sieh' weiter.
Aber wie? Wenn ich den entferne funktioniert gar nichts mehr. Und wie 
ich den weiter optimieren kann weiß ich nicht.

>
> Was hier einfach zweifelhaft ist, ist das ein Programm
> geschwindigkeitsmäßig durch die compilereigene Optimierung so hingebogen
> werden soll, dass es schnell genug ist um zu funktionieren. Das kann ein
> Profi in Ausnahmefällen sicherlich mal machen, wenn es anders garnicht
> mehr geht. Aber wenn das nötig ist, wenn ein Anfänger was schreibt, dann
> ist irgendwas grundsätzlich faul, denke ich. Das Programm muss so
> parametriesierbar sein, das es (in den Grenzen des Grundsätzlichen) mit
> jeder Frequenz geht.
>
Als Parameter fallen mir grundsätzlich nur ein:
- Maximale SPI Frequenz für den SPI Slave 2.5 MHz
- Rate mit der der SPI Slave die Daten "verbraucht" 49 kbps
- Der Slave braucht pro Datenbyte noch ein Controllbyte.
Daraus folgt für mich, das ich bei 6.4 MHz maximal
(6.4MHz Inst/sek) / ((49 kb/s) * 2) = 65 Instruktionen / bit
brauchen darf. Das ist nach meiner Analyse der einzige Punkt im Program 
bei den es auf die Geschwindigkeit ankommt.
War das mit parametriesierbar  gemeint, oder hab ich da was falsch 
verstanden?

>>Ich denke nicht, dass die maximale Frequenz das Problem ist, sondern die
>>minimale.
> Von was für einer minimalen oder maximalen Frequenz redest Du?

Die maximale SPI Frequenz ist denke ich nicht das Problem, da ich 
maximal mit 2.5 MHz die Daten rausschicken darf, da bin ich auf jeden 
Fall drunter. Ich muss aber mindestens 98 kb/s an den Slave liefern, 
damit es bei der FIFO keine Probleme gibt.

>
> Ich lese hier
>
>>Ich tippe mal,...
>>... Verdacht ...
>>Ich denke ...
>>Ich denke nicht, ...
>>Ich nehme an...
>
> Nimm nicht nur an, sondern prüfe nach. Denke nicht nur, sondern ermittle
> Fakten. Nicht tippen, das ist hier kein Glücksspiel.
>
Mein Problem ist halt das wie. Ich hab leider nur sehr eingeschränkte 
Möglichkeiten hier, leider hab ich noch kein Oszilloskop oder Logic 
Analysator, mit den ich mir genauer angucken könnte was zwischen den 
Master und dem Slave passiert. Deshalb versuche ich gerade anhand der 
Symptone auf die wahrscheinlichste Fehlerursache zu schließen.
Vielleicht gibt es ja auch noch andere Möglichkeiten das zu tun, aber 
mir fallen zur Zeit keine ein.

> Darauf will ich Dich nur mal aufmerksam machen. Ich meine es nicht böse,
> will Dich nicht runtermachen oder so. Ich meine das ganz freundlich,
> aber die Wahrheit klingt nunmal nicht immer angenehm.

Keine Sorge, ich freu mich über alles was mir weiter hilft, auch wenn es 
mir hilft mir selbst zu helfen :)
Vielen Dank auf jeden Fall das du dir überhaupt die Mühe machst.

>
> Bisher kommt mir Dein Post vor wie eines der oft hier anzutreffenden, wo
> jemand sich nicht die Mühe macht oder vielleicht auch nicht in der Lage
> ist, gedanklich, d.h. sprachlich und/oder bildlich stringent vorzugehen.
> Erwartest Du das sich hier jemand mit diesen Andeutungen, Vermutungen
> und Annahmen Deinen Code anschaut um ein Problem zu finden, das
> überhaupt nicht definiert ist.
>
> Deswegen der Tip. Finde erstmal heraus wo genau der Flaschenhals liegt.
> Finde erstmal heraus, was bei der Optimierung passiert, was Deinen Code
> unbrauchbar macht. Das machen wir hier im allgemeinen nicht für Dich.
> (Es gibt allerdings hier Leute die sich auch diese Mühe machen).

Mein größtes Problem ist halt gerade das herausfinden, wo der 
Flaschenhals liegt. Ich bin gerade dabei mir den Assembler Code 
anzugucken, aber gerade auf avr hab ich damit noch nicht wirklich viel 
gemacht. Der Tiny schränkt mich auch ein bischen ein, da da alle Pins 
besetzt sind, und nichts mehr für irgendwelche Debug LEDs übrigbleibt.
auf einen größeren Chip kann ich das Programm schlecht überspielen, weil 
ich keinen da habe, der intern eine 6.4 MHz Referenz hat und bei 8 MHz 
funktioniert ja alles.

von Uwe (de0508)


Lesenswert?

Hallo,

ich habe gerade die main.c überflogen.

|rfm12_interrupt()| würde ich als Macro anlegen, so wird der Code in der
1
ISR(INT0_vect) {
2
3
  rfm12_interrupt();
4
5
}

schneller und kleiner.

Wird das |cli();| in |rfm12_interrupt();| gebraucht ?

Die Macros
1
  SENSOR_VCC_OUT();
2
3
  SENSOR_VCC_LOW();
4
5
  SENSOR_1_IN();
6
7
  SENSOR_1_LOW();
8
9
  SENSOR_2_IN();
10
11
  SENSOR_2_LOW();

greifen jedes mal lesend und schreiben auf den Port bzw. DDR zu.

Über je eine Puffervar. für den port und das ddr spart man einige 
Zugriffe und somit Zeit.

Bei den dummy ADC-Readout müssen die Rückgabewerte nicht gespeichert 
werden !

z.B.:
1
temp1 = adcRead(SENSOR_INTERNAL_CHANNEL);

Eine weitere Kleinigkeit kann man in diesen Blöcken sparen:
1
send_buffer[0] = gap   >> 8;
2
3
send_buffer[1] = gap   & 0xFF;

Optimiert:
1
send_buffer[0] = (uint8_t)(gap >> 8);
2
send_buffer[1] =  (uint8_t)gap;

und
1
for( i = 0; i < 8; i++)
2
3
    send_buffer[i] = 0;
ist evident und kann gelöscht werden !

Falls der C Compiler dies nicht included,
1
void loop(void) { .. }

dann wäre es besser die Funktion so zu schreiben:
1
static inline void loop(void) { .. }

Das spart einige push / pop Anweisungen.

Das war's mal auf die Schnelle.

von Ulrich (Gast)


Lesenswert?

Normal sollte ein Programm mit allen Optimierungsstufen (O0,O1,O2 und 
Os) laufen. Die Delay Routinen von GCC gehen ohne Optimierung nicht - 
das ist aber eine Ausnahmen. Wenn es mit Optimierung nicht geht, ist das 
oft so etwa wie ein Fehlendes volatile oder ein Wartescchleife die 
abhanden kommt.

Bei einem Fehler mit den Sleep mode weiss man ja schon recht genau wo es 
hakt - und sollte den Fehler im ASM Listing gut finden können.

Ein paar Ansatzpunkte zur Optimierung:
In dem Code wird in der ISR eine Funktion aufgerufen. Wenn man das 
vermeiden kann, z.B. als Inline erzwingen, kann der Code deutlich 
schneller und wohl auch kürzer werden. In der ISR werden so meist 
unnötig viele Register gerettet.

Die Funktion Loop wird nur einmal in Main gebraucht, so etwas sollte 
besser  als Static inline deklariert werden, oder gleich ohne 
Funktionsdefinition gemacht werden.

In Main werden Word variablen in Bytes zerlegt. Das geht vermutlich 
schneller, aber auch weniger Portabel, mit der Definition einer UNION.

Bei der Einstellung des ADMUX wird viel mit &= und |= gearbeitet. Oft 
kann man aber auch den ganzen Wert neu schreiben.

von Thomas (kosmos)


Angehängte Dateien:

Lesenswert?

du könntest einen 74HC(T)573 als Latch hernehmen, mit einigen 
Steuerleitungen kannst du so einen weiteren 8bit Port darstellen. Das 
Latch lässt sich hochohmig stellen so das es deinen Port nicht mehr 
beeinflusst und du ihn dann wieder für andere Sachen(Debugausgaben) 
verwenden kannst inkl. ihn wieder als Eingangsport zu nutzen.

Anhang ist etwas einfach gefasst, am besten mal das Datenblatt 
runterladen

von Christian H. (netzwanze) Benutzerseite


Lesenswert?

Thomas O. schrieb:
> du könntest einen 74HC(T)573 als Latch hernehmen,

Hääh?
Was hat das mit Optimierung zu tun?

von Thomas (kosmos)


Lesenswert?

Verwirrter Anfänger schrieb:
> Der Tiny schränkt mich auch ein bischen ein, da da alle Pins
> besetzt sind, und nichts mehr für irgendwelche Debug LEDs übrigbleibt.

darauf bezog ich meine Empfehlung für einen Latch. Ich habe z.B. mal mit 
einem ATTiny26 und einem Latch mehrere ADC-Kanäle genutzt und zusätzlich 
ein LCD im 8 Bit Modus angesteuert und hätte die 8 Pins fürs LCD 
zusätzlich noch anderst nutzen können z.B. abwechselnd auch als 
Eingänge.

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.