Wert an Variable per Referenz übergeben

Gast #2253172
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
#2253198
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.
Gast #2253226
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 ...
Gast #2253242
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"?
#2253274
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).
#2253283
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.
Gast #2253306
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
#2253331
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.
#2253364
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.
Gast #2253420
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?
#2253424
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)
#2253431
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.
Gast #2253433
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
#2253445
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.
Gast #2253461
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
#2253475
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.
#2253528
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.
Gast #2253550
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
#2253572
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.
Gast #2253708
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.
Moderator Persönliche Seite #2253724
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren