Optimierung von Schleifen?

by Dr. Stack van Hay | 10. August 2013 09:05

In einem Elektronikforum bin ich auf das folgende Statement gestoßen, dass trotz seiner vulgären Formulierung eine untersuchenswerte Frage aufwirft:
[quote]
PeDa Entprellung ernst gemeint?[1]
Autor: Steel (Gast)
Datum: 


Der Code ist ja sowas von schlechter Stil, dass ich beinahe auf die Tastatur gekotzt hätte. So in etwa muss man 1977 programmiert habe wenn man frisch von der Uni war und zeigen wollte, dass man C kann. Und sowas setzt man hier Anfängern vor um deren Stil zu versauen?Ich sag nur Einrückungen, for(;;), break;,if( –i == 0 ), wofür das
alles? Code obfuscation?
[/quote]

Der Punkt, der mich daran interessiert, ist:
[note]Ist es wohl schneller, eine Schleife durch Rückwärtszählen der Laufvariable und Test auf 0 zu konstruieren, als durch Vorwärtszählen der Laufvariable und Test auf die Obergrenze?[/note]

Unlogisch wäre es nicht unbedingt, wenn ein Test auf 0 schneller wäre, als der Vergleich mit einem oberen Grenzwert. Schön wäre es natürlich wirklich, wenn sich der Compiler um solchen Fummelkram möglichst gut selber kümmern würde, denn dann könnte ich einfach so programmieren, dass ich das Programm vielleicht auch noch ein paar Jahre später verstehe und der Compiler würde sich um die ganze plattformspezifischen Details kümmern und aus lesbarem und wartbarem Code trotzdem ordentliche ausführbare Dateien erzeugen.

Der Quelltext

So einfach war es für mich gar nicht ein Beispiel zu finden, dass einigermaßen repräsentativ ist, natürlich will ich möglichst wenig „Kram“ im Assemblercode haben, damit der Vergleich nicht zu unübersichtlich wird. Andererseits führen auch ältere Compiler in Sonderfällen schon einige Optimierungen an der fraglichen Schleifenstruktur durch und merken es vor allem, wenn Codefragmente eindeutig überflüssig sind.
Der folgende Code ist also nicht Sinn-voll, er tut nichts produktives, ist aber sehr kurz, trifft keinen der Sonderfälle und wird nicht wegoptimiert:

[code]
uint16_t states_read;

states_read = 49102;
while(1)
{
asm volatile ("nop");
if(–states_read==0)  // Zählt nur bis 1 runter!
break;
}

states_read = 1;
while(1)
{
asm volatile ("nop");
if(states_read++==49102)
break;
}
[/code]

Compiliert habe ich einmal mit avr-gcc Version 4.3.2 aus der aktuellen (August 2013) Arduino-Distribution 1.0.5 und mit einem normalen avr-gcc 4.6.2. Das Makefile war in beiden Fällen bis auf

[code]COMPILE = …/Arduino.app/…/avr-gcc -Wall -Os -DF_CPU=$(CLOCK) -mmcu=$(DEVICE)[/code]

beziehungsweise

[code]COMPILE = avr-gcc -Wall -Os -DF_CPU=$(CLOCK) -mmcu=$(DEVICE)[/code]

identisch.

AVR-GCC 4.3.2

[one-half]

[code]
ldi r24, 0xCE ; 206
ldi r25, 0xBF ; 191
nop
sbiw r24, 0x01 ; 1
brne .-6 ; 0x138 <main+0xc>
[/code]

[/one-half]

[one-half last]

[code]
ldi r24, 0x01 ; 1
ldi r25, 0x00 ; 0
nop
adiw r24, 0x01 ; 1
ldi r18, 0xBF ; 191
cpi r24, 0xCF ; 207
cpc r25, r18
brne .-12 ; 0x142 <main+0x16>
[/code]

[/one-half]

[one-half]7 Takte pro normalem Durchlauf[/one-half][one-half last]9 Takte pro normalem Durchlauf[/one-half]

AVR-GCC 4.6.2

[one-half]

[code]
ldi r24, 0xCE ; 206
ldi r25, 0xBF ; 191
nop
sbiw r24, 0x01 ; 1
brne .-6 ; 0x138 <main+0xc>
[/code]

[/one-half]

[one-half last]

[code]
ldi r24, 0xCE ; 206
ldi r25, 0xBF ; 191
nop
sbiw r24, 0x01 ; 1
brne .-6 ; 0x142 <main+0x16>
[/code]

[/one-half]

[one-half]7 Takte pro normalem Durchlauf[/one-half][one-half last]7 Takte pro normalem Durchlauf[/one-half]

[clear]

Fazit

Damit ist das Thema wohl gegessen. Der aktuellere Compiler optimiert die Schleife automatisch, egal wie sie im Code formuliert ist. Es spricht damit aus meiner Sicht nichts mehr dafür, bei der Formulierung solcher Zählschleifen nur aus Gründen der Optimierung auf die Dekrement-gegen-Null Variante zu setzen.

Endnotes:
  1. PeDa Entprellung ernst gemeint?: http://www.mikrocontroller.net/topic/304125#3262379

Source URL: https://drsvanhay.de/atmel-avr-optimierung-von-schleifen/