Forum: Mikrocontroller und Digitale Elektronik Wert an Variable per Referenz übergeben


von *Hansi (Gast)


Lesenswert?

Hallo.
Ich möchte einen Wert an eine Variable übergeben.
Nun habe ich mich gefragt, welche der beiden Versionen schneller bzgl. 
der Ausführzeit ist:
1
unsigned int RegForPointer=0,*ptrReg;
2
unsigned int RegForNormal=0;
3
ptrReg=&RegForPointer;
4
5
int main(void)
6
{
7
  
8
9
/* Version A*/
10
*ptrReg=5;
11
*ptrReg=10;
12
13
/* Version B*/
14
RegForNormal=5;
15
RegForNormal=10;
16
17
}
Ist Version A oder Version B schneller?
Natürlich nicht gerade ein riesen Codebeispiel, aber es geht ja ums 
Prinzip.

Viele Grüße

von Frank (Gast)


Lesenswert?

compilieren und Listing anschauen!

von *Hansi (Gast)


Lesenswert?

Frank schrieb:
> compilieren und Listing anschauen!


Was würdest du denn aus dem Bauch heraus sagen? :)
Habe ich gemacht, desswegen frage ich ja.

von Martin (Gast)


Lesenswert?

Compilieren und Simulator laufen lassen

von (prx) A. K. (prx)


Lesenswert?

Bei solchen Performance-Tests solltest du drauf achten, dass das 
Beispiel wirklich sehr gut dem später produktiv verwendeten Code 
entspricht. Also beispielsweise dass es sich durchweg um globale 
Variablen handelt und der Zugriff auf die Variablen exakt einmal 
erfolgt.

Sonst kann es nämlich leicht passieren, dass das Ergebnis dir nicht 
wirklich weiter hilft. Nicht selten geschieht es auch, dass Testcode vom 
Compiler als überflüssiger Unfug erkannt und zusammengestrichen wird.

Beispiel: Direkte Adressierung ist dann schneller, wenn sie ein einziges 
Mal erfolgt. Wird auf die gleiche Variable zigmal zugegriffen kann die 
indirekte Variante besser sein. Vorausgesetzt natürlich, die ungenannte 
Zielmaschine hat überhaupt eine nennenswerte Anzahl Register. Und 
verschiedene Compiler können zu sehr verschiedenen Erkenntnissen führen.

von *Hansi (Gast)


Lesenswert?

A. K. schrieb:
> Direkte Adressierung

Also unter direkter Adressierung versteht du *ptr,korrekt?

von (prx) A. K. (prx)


Lesenswert?

*Hansi schrieb:

> Also unter direkter Adressierung versteht du *ptr,korrekt?

Nein. Was ist daran direkt? Durch einen Pointer hindurch heisst 
indirekt.

von CPU Kontrolleur (Gast)


Lesenswert?

Und noch was: Nicht jede CPU unterstützt die willkürliche Mischung 
verschiedene Adressierungsarten. Es kommt also auch auf die 
Vorgeschichte. Welcher Wert kommt wo her? Konstanten, Register, mem ...

von *Hansi (Gast)


Lesenswert?

Dann ist ein etwas komplexerer uC wohl auch soetwas wie eine Blackbox?
Wenn es auf die Vorgeschichte, etc. ankommt, wie gewährleistet man dann 
den effektivsten (also schnellsten) Code geschrieben zu haben?
Gibt es nicht soetwas wie eine "Richtlinie"?

von (prx) A. K. (prx)


Lesenswert?

*Hansi schrieb:

> Dann ist ein etwas komplexerer uC wohl auch soetwas wie eine Blackbox?
> Wenn es auf die Vorgeschichte, etc. ankommt, wie gewährleistet man dann
> den effektivsten (also schnellsten) Code geschrieben zu haben?

Durch Rückkopplung. C-Code schreiben, erzeugten Code inspizieren, C-Code 
verändern, ...

> Gibt es nicht soetwas wie eine "Richtlinie"?

Entsteht durch obigen Prozess. Es hilft allerdings auch, wenn man dank 
Erfahrung mit Compilern und Zielmaschinen eine Vorstellung von deren 
Denkweise und der Umsetzung von C-Code in Maschinencode hat.

Es ist etwas mehr als nur eine Richtlinie und abhängig sowohl vom 
Compiler als auch von der Zielarchitektur, manchmal auch vom 
tatsächlichen Zielprozessor (also beispielsweise Pentium III vs 4).

von (prx) A. K. (prx)


Lesenswert?


von Karl H. (kbuchegg)


Lesenswert?

Gerade Anfänger legen meiner Meinung nach viel zu viel Augenmerk auf 
low-level Optimierungen.
Compilerbauer sind nicht doof. Die haben ihrem Compiler 
Optimierungsstrategien mitgegeben, mit denen er schon sehr viel aus dem 
Code herausholen kann.

Als Neuling sollte man sich im ersten Anlauf darauf beschränken, die 
jeweils sinnvollen kleinstmöglichen Datentypen auszuwählen. Damit 
erreicht man schon eine ganze Menge. Und für den Rest gilt: In erster 
Linie zeichnet sich guter Code dahingehend aus, dass er sauber und 
lesbar ist.

Mit der Zeit wird man dann sowieso besser. Man verwendet bessere 
Verfahren, bessere und schnellere Algorithmen, hat eine Menge 
algorithmischer Tricks auf Lager, die bei Bedarf eingesetzt werden. Erst 
dann ist der Zeitpunkt gekommen, an dem man dann tatsächlich anfängt im 
Assembleroutput des Compilers nachzusehen, ob man noch den einen oder 
anderen Taktzyklus einsparen kann. Aber erst dann, wenn zuvor alles 
andere ausgeschöpft wurde.

Auch ein solides Grundverständnis, wie die Sprache C funktioniert bringt 
allemal mehr, als so mancher 'Tip' in diesem Optimierungslink.

von *Hansi (Gast)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Erst
> dann ist der Zeitpunkt gekommen, an dem man dann tatsächlich anfängt im
> Assembleroutput des Compilers nachzusehen, ob man noch den einen oder
> anderen Taktzyklus einsparen kann

Tja, und der ist wohl gekommen.
Habe für ein Projekt alles "quick & dirty" programmiert, globale/lokale 
Variablen immer direkt verändert/übergeben, usw.
Das Programm hat wunderbar funktioniert, die Ausführzeiten wahren sehr 
gut.
Irgendwann wollte ich dann mal den Code ein bisschen säubern, habe es 
teilweis umgebaut und Pointer verwendet.
Und nun könnte ich gerade *kotz...* die Ausführzeit hat sich verdoppelt.

Ich habe damals an der Uni gelernt (also noch zu Zeiten von uC/51), dass 
man bei Controllern generell viel mit Pointern arbeiten soll.
Nun im Jahr 2011 angekommen, mein erster 32-Bitter, scheint das wohl 
nicht mehr so wirklich der Fall zu sein.
Wenn man bereits beim ersten Versuch über einen solchen Fallstrick 
stolpert, kann es nur besser werden.

Vielen Dank für eure Mühen,
Hans Peter

von Karl H. (kbuchegg)


Lesenswert?

*Hansi schrieb:

> Irgendwann wollte ich dann mal den Code ein bisschen säubern, habe es
> teilweis umgebaut und Pointer verwendet.
> Und nun könnte ich gerade *kotz...* die Ausführzeit hat sich verdoppelt.

Logisch.

Wenn du immer nur einen Zettel vorfindest, auf dem steht "Die 
Information steht im Regal 5, 3. Zettel von links", dann brauchst du 
doppelt so lange. Erst mal musst du den ersten Zettel lesen und dann mit 
dieser Information den anderen Zettel auffinden, auf dem dann die 
tatsächliche Information steht. Du hast also 2 Zugriffe, wo es einer 
auch tut, wenn du von vorne herein schon weißt, dass du im Regal 5 
nachsehen musst. Das weiß aber der Compiler/Linker, denn die haben ja 
die Variablen im Speicher angeordnet.

> Ich habe damals an der Uni gelernt (also noch zu Zeiten von uC/51), dass
> man bei Controllern generell viel mit Pointern arbeiten soll.

Unsinn.

> Nun im Jahr 2011 angekommen, mein erster 32-Bitter, scheint das wohl
> nicht mehr so wirklich der Fall zu sein.

Dieser "Rat" war auch damals schon Unsinn.
Man verwendet Pointer, wenn man sie verwenden muss. Und wenn es nicht 
sein muss, dann lässt man es.

von (prx) A. K. (prx)


Lesenswert?

*Hansi schrieb:

> Nun im Jahr 2011 angekommen, mein erster 32-Bitter, scheint das wohl
> nicht mehr so wirklich der Fall zu sein.

An dieser Stelle unterscheiden sich 32-Bitter ziemlich deutlich von 
vielen 8-Bittern.

Daumenregel: Bei 32-Bittern ist der Zugriff auf globale Variablen viel 
teurer als der Zugriff auf lokale Variablen. Globale Variablen sollten 
daher nur verwendet werden wo wirklich nötig. Soweit möglich lokale 
Variablen verwenden. Parameterübergabe ist oft effizienter als die 
Verwendung globaler Variablen.

Thematisch zusamenhängende globale Variablen können effizienter sein, 
wenn sie zu einer struct zusammengefasst werden. Je nach Intelligenz des 
Compilers und Häufigkeit des Zugriffs kann es sich dann auch lohnen, 
die Adresse dieser struct anfangs in einer Funktion in einen Pointer zu 
laden und über diesen Pointer zuzugreifen.

Ein globaler Pointer als "Optimierung" für Zugriff auf die immer gleiche 
bekannte Variable ist wohl immer Unfug.

von Johannes F. (Gast)


Lesenswert?

Also sind call-by-reference-Aufrufe von Funktionen auch insgesamt nicht 
immer schneller als call-by-value?
Denn bei Übergabe der Adresse einer Variablen als Argument muss zwar 
deren Wert nicht kopiert werden, dafür dauern aber die Zugriffe per 
indirekter Adressierung in der Funktion länger?

von Karl H. (kbuchegg)


Lesenswert?

Johannes F. schrieb:
> Also sind call-by-reference-Aufrufe von Funktionen auch insgesamt nicht
> immer schneller als call-by-value?

Das hängt vom Datentyp des Arguments ab.

> Denn bei Übergabe der Adresse einer Variablen als Argument muss zwar
> deren Wert nicht kopiert werden, dafür dauern aber die Zugriffe per
> indirekter Adressierung in der Funktion länger?

Genau.
Wenn daher die Kopie schneller erstellt ist, als die indirekten Zugriffe 
dauern, dann hast du pessimiert.


Edit: Ausnahme natürlich, wenn die Funktion in der Lage sein soll, das 
Argument beim Aufrufer zu verändern. Dann hast du sowieso keine andere 
Wahl und musst Pointer benutzen. Aber darum gehts ja nicht (denke ich 
zumindest)

von (prx) A. K. (prx)


Lesenswert?

Johannes F. schrieb:

> Denn bei Übergabe der Adresse einer Variablen als Argument muss zwar
> deren Wert nicht kopiert werden, dafür dauern aber die Zugriffe per
> indirekter Adressierung in der Funktion länger?

Zugriffe über Adressparameter sind meist effizienter als direkte 
Zugriffe auf globale Variablen. Allerdings kann es noch besser sein, den 
Wert der Variablen direkt zu übergeben und den neuen Wert als Returnwert 
zurück zu bekommen, wenn in der Funktion öfter darauf zugegriffen wird.

von Klaus (Gast)


Lesenswert?

A. K. schrieb:
> Daumenregel: Bei 32-Bittern ist der Zugriff auf globale Variablen viel
> teurer als der Zugriff auf lokale Variablen. Globale Variablen sollten
> daher nur verwendet werden wo wirklich nötig. Soweit möglich lokale
> Variablen verwenden. Parameterübergabe ist oft effizienter als die
> Verwendung globaler Variablen.

Das würde ich so nicht kaufen. Die Adresse einer globalen Variable kann 
schon der Linker einsetzen, die lokale Adresse muß immer zur Laufzeit 
relativ zum Framepointer berechnet werden.

A. K. schrieb:
> Ein globaler Pointer als "Optimierung" für Zugriff auf die immer gleiche
> bekannte Variable ist wohl immer Unfug.

Da bin ich bei dir, indirekt ist eben indirekt.

MfG Klaus

von (prx) A. K. (prx)


Lesenswert?

Klaus schrieb:

> Das würde ich so nicht kaufen.

Solltest du aber.

> Die Adresse einer globalen Variable kann
> schon der Linker einsetzen, die lokale Adresse muß immer zur Laufzeit
> relativ zum Framepointer berechnet werden.

Ich beziehe mich ausdrücklich auf 32-Bitter. Zugriffe relativ zum Stack- 
oder Framepointer sind dort stets einfache Standardbefehle. Die 
Adressrechnung kostet nichts extra sondern ist Teil der Pipeline. 
Zugriffe auf globale Daten hingegen müssen bei RISCs (ARM/Cortex, AVR32, 
PIC32, ...) aus mehreren Befehlen zusammengesetzt werden und sind auch 
bei CISCs (Coldfire, x86) vom Code her länger als die Register-relative 
Adressierung.

Ausserdem landen lokale Variablen und Parameter bei 32-Bittern meist in 
Registern, nicht auf dem Stack. Zumindest wenn es nicht zu viele sind 
und sie keine Adresse haben müssen. Und Register sind die mit Abstand 
effizienteste Speicherung.

von Klaus (Gast)


Lesenswert?

A. K. schrieb:
> Ich beziehe mich ausdrücklich auf 32-Bitter. Zugriffe relativ zum Stack-
> oder Framepointer sind dort stets einfache Standardbefehle. Zugriffe auf
> globale Daten hingegen müssen bei RISCs (ARM/Cortex, AVR32, PIC32, ...)
> aus mehreren Befehlen zusammengesetzt werden und sind auch bei CISCs
> (Coldfire, x86) länger codiert.

Bei (den großen) RISCs stimme ich dir zu, hättest du aber gleich sagen 
können. Bei den kleinen kann das aber anders sein.

Auf meinem Atom (x86) gibt es keinen signifikanten Laufzeitunterschied 
zwischen der Verwendung von lokalen oder globalen Variablen.

MfG Klaus

von (prx) A. K. (prx)


Lesenswert?

Klaus schrieb:

> Bei (den großen) RISCs stimme ich dir zu, hättest du aber gleich sagen
> können. Bei den kleinen kann das aber anders sein.

Dass ich 32-Bitter meinte stand doch direkt im Text drin. Welche 
kleineren 32-Bitter hast du hier im Auge? Viel kleiner als ARM gibts 
fast nicht. Allenfalls Zilogs irre 32-Bit Z80.

> Auf meinem Atom (x86) gibt es keinen signifikanten Laufzeitunterschied
> zwischen der Verwendung von lokalen oder globalen Variablen.

Ach je! Ich bringe als Beispiele fast ausschliesslich typische 32-Bit 
Mikrocontroller (ARM, Cortex, AVR32, PIC32, Coldfire), die du 
seltsamerweise "grosse RISCs" nennst. Und du kommst mit dem sehr sehr 
viel grösseren Intel Atom. Was soll das denn?

NB: Ja, beim Atom ist relative vs. absolute Adressierung meist neutral, 
oder fast. Trotzdem ist die relative Adressierung meist 3 Bytes kürzer 
als die absolute und (mindestens) genauso schnell.

x86 hat ein bischen wenig Register, was entsprechende Optimierung 
erschwert. Je nach ABI gehören die ausserdem zu den wenigen 32-Bittern, 
die Parameter konsequent per Stack übergeben. Bei AMD64 (64-Bit x86) 
sieht das schon ganz anders aus.

von Klaus (Gast)


Lesenswert?

A. K. schrieb:
> Dass ich 32-Bitter meinte stand doch direkt im Text drin

Ok, ich merke mir 32 Bit heißt RISC bei dir.

A. K. schrieb:
> die du
> seltsamerweise "grosse RISCs" nennst

Die AVRs kenne ich nicht so genau, aber PIC kleiner als 32 würde ich als 
kleine RISCs bezeichnen.

Und der Atom ist der kleinste gängige x86 heutzutage.

MfG Klaus

von (prx) A. K. (prx)


Lesenswert?

Klaus schrieb:

> Ok, ich merke mir 32 Bit heißt RISC bei dir.

Ich hatte sowohl Coldfire als auch x86 ausdrücklich erwähnt, beides 
CISC. Für beide gilt diese Regel ebenfalls, wenngleich mitunter in 
geringerem Ausmass. Auch da sind Register effizienter als Speicher und 
auch da macht kleinerer Code irgendwann einen Unterschied.

Es gibt übrigens noch weitere Aspekte: Lokale Variablen sind auch sonst 
besser optimierbar, weil der Compiler meist weiss, dass niemand sonst 
darauf zugreift. Globale Variablen lassen sich über Aufrufe von 
Funktionen hinweg meist nicht optimieren, weil der Compiler nicht weiss, 
ob die Variablen darin verwendet werden. Dazu kommen noch mögliche 
Aliasing-Risiken, die auch fast nur globale Variablen betreffen (bei 
Skalaren).

> Die AVRs kenne ich nicht so genau, aber PIC kleiner als 32 würde ich als
> kleine RISCs bezeichnen.

Aber die würde ich nicht als 32-Bitter bezeichnen. Und ich hatte meine 
Aussage ausdrücklich auf 32-Bitter bezogen.

Darüber hinaus hatte ich es als Daumenregel bezeichnet, nicht als 
ehernes Gesetz. Allerdings würde ich gern mal ein realistisches Beispiel 
sehen, wo es mit globalen Variablen deutlich besser funktioniert als mit 
lokalen.

> Und der Atom ist der kleinste gängige x86 heutzutage.

Liegt aber immer noch Welten oberhalb der Mikrocontroller. Für das was 
hier besprochen wird ist der Unterschied zwischen Atom und Sandy Bridge 
wenig relevant.

von Klaus (Gast)


Lesenswert?

A. K. schrieb:
>> Die AVRs kenne ich nicht so genau, aber PIC kleiner als 32 würde ich als
>> kleine RISCs bezeichnen.
>
> Aber die würde ich nicht als 32-Bitter bezeichnen. Und ich hatte meine
> Aussage ausdrücklich auf 32-Bitter bezogen.

War auch nur die Antwort auf deine Frage, was ich als "nicht" große 
RISCs bezeichnen würde.

Aber etwas zurück zum Thema: Variablen sollten da angelegt werden, wo 
sie nach der Logik des Programms hingehören, und Pointer sollten benutzt 
werden, wenn die Programmlogik einen Pointer erfordert. Alles andere 
rächt sich, wenn man in einem Jahr was am Programm ändern muß. Ansonsten 
sollte man eine schnellere CPU nehmen.

MfG Klaus

von (prx) A. K. (prx)


Lesenswert?

Klaus schrieb:

> Aber etwas zurück zum Thema: Variablen sollten da angelegt werden, wo
> sie nach der Logik des Programms hingehören, und Pointer sollten benutzt
> werden, wenn die Programmlogik einen Pointer erfordert.

D'accord.

Ich hatte das angesprochen, weil manche Leute von 8-Bittern her gewohnt 
sind, dass lokale und globale Variablen kaum einen Unterschied machen. 
Manchmal sogar globale/statische Variablen besser sind als lokale: z.B. 
beim PIC18 mit nicht erweiterten Befehlssatz und lokalen Daten auf dem 
Stack, wie das der C18 in Standardeinstellung realisiert - aber damit 
nicht wirklich gut zurecht kommt.

Da kann es dann passieren, das gewohnheitsmässig sehr viele Daten global 
liegen, ob von der Programmlogik her sinnvoll oder nicht. Wenn man dann 
auf beispielsweise Cortex-M3 wechselt, dann kann sich dieser Stil als 
ungünstig erweisen.

von MCUA (Gast)


Lesenswert?

>> Ich habe damals an der Uni gelernt (also noch zu Zeiten von uC/51), dass
>> man bei Controllern generell viel mit Pointern arbeiten soll.
>Unsinn.
Er meinte wohl kleinere Controller, die nicht so viel ProgramSpeicher 
mit sich rum schleppen können.

>Zugriffe auf globale Daten hingegen müssen bei RISCs (ARM/Cortex, AVR32,
>PIC32, ...) aus mehreren Befehlen zusammengesetzt werden
Nicht, wenn bereits ein Register auf eine glob. Tabelle zeigt

>Und noch was: Nicht jede CPU unterstützt die willkürliche Mischung
>verschiedene Adressierungsarten.
willkürliche Mischung?
entweder die CPU kann die Adressierungsart, oder sie kann es nicht.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

*Hansi schrieb:
> Ich habe damals an der Uni gelernt (also noch zu Zeiten von uC/51), dass
> man bei Controllern generell viel mit Pointern arbeiten soll.

Das musst Du falsch in Erinnerung haben. Diese pauschale Aussage ist 
schlichtweg falsch... und sie war schon immer falsch - nicht nur damals.

Die Verwendung eines Pointers kann eine Geschwindigkeitssteigerung beim 
Zugriff auf Array-Elemente zur Folge haben, insbesondere dann, wenn das 
Array mehrdimensional ist. Denn dann muss die CPU u.U. einige 
Multiplikationen durchführen, um auf das Element des Arrays a[i][j][k] 
zuzugreifen. Willst Du da linear auf nebeneinanderliegende Elemente 
(z.B. in einer Schleife) zugreifen, bist Du mit einem Pointer besser 
bedient. Den brauchst Du nur zu inkrementieren. Aber auch das kriegen 
gute C-Compiler meistens auch selbst hin, d.h. sie verwenden insgeheim 
Pointer, obwohl Du mit Array-Indices arbeitest.

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.