Hallo,
eh die ersten kommen und sagen C und Zeitkritisch beißt sich, ich weiß
Assembler ist schneller.
Es geht darum, dass ich bis jetzt nur kleine Projekte bearbeitet habe,
bei denen es weder auf das eine noch auf das andere ankam. Jetzt gibts
ein größeres Projekt mit einer Ethernetverbindung. Der Datenaustausch
soll natürlich so schnell wie möglich vor sich gehen. Deshalb die Frage,
nimmt es groß Zeit weg, wenn ich zum Beispiel einen Wert erst in eine
Variable schreibe, diese einer Funktion übergebe, die ihn dann erst in
den Sendepuffer schreibt, oder schlimmsten Falls noch mal eine Funktion
aufruft, die etwas damit tun soll? Ich kenne das z.B. von JAVA Projekten
auf dem PC, dass man zu jedem "Schiss" ne Methode schreibt um das ganze
so modular und lesbar wie möglich zu machen. C kommt mir bis jetzt immer
noch recht kryptisch daher. Was denkt ihr dazu, wie programmiert ihr so,
worauf sollte ich achten, außer natürlich, dass die File spezifisch der
Aufgabe sein sollten.Danke für eure Antworten, Links sind natürlich auch
sehr willkommen, lässt sich nur schlecht googlen das Ganze.
Damit meine ich eigentlich nur, das man Files spezifisch zu ihren
Aufgaben erstellt und diese Konvention dann einhält um den Überblick zu
behalten, bzw. nach Aufgaben zu trennen.
Wenn man Protokolle in Hochsprachen schreibt und die Implementierung
gleichzeitig schnell und modular sein soll, sollte man darauf achten,
daß so wenig wie möglich umherkopiert werden muß. Das kann man in "C"
oder "C++" dadurch erreichen, daß man Puffer präalloziert, die so groß
sind, daß jede Schicht ihre Daten einfach dazupacken kann. Wenn man jede
Schicht als Klasse sieht, kann eine Funktion in jeder Schicht darüber
Auskunft geben, wieviel Platz ihre Verwaltungsdaten einnehmen; wie groß
also ihre Header und Trailer sind, und demnach wo die jeweils obere
Schicht anfangen kann, ihre Daten zu schreiben. Das hat auch den
Vorteil, daß man mit Blick auf das OSI-Modell Schichten
dazwischenschieben oder weglassen kann. Die jeweils oberste oder
unterste Schicht präalloziert in der Initialisierungsphase mit diesen
Daten dann einen oder mehrere Puffer. (Auf denglish nennt sich diese
Technik "Overlapped-IO") Der Pufferaufbau sieht damit z.B. so aus:
Abhängig von der Leistungsfähigkeit der Zielhardware kann es sinnvoll
sein, nach der Hochsprachenimplementierung (wenn alles funktioniert)
einzelne Routinen in Assembler zu schreiben. In diesem Zusammenhang
bieten sich vor allem die kurzen Prüfsummen-Routinen an. Abhängig vom
verwendeten Compiler kann man meiner Erfahrung nach einen Faktor von 2-5
erreichen.
Speicherkritische Fälle, in denen der Speicher kaum für einen einzelnen
Puffer reicht, habe ich selbst noch nicht in den Fingern gehabt. Aber es
gibt für diese eine ganze Reihe freie, in "C" implementierte
Mini/Micro-IP Stacks mit nur wenig eingeschränkter Funktionalität.
Das kann man meist nur beurteilen wenn man das "Compilat" sieht.
über verschiedene Techniken (Call by referece, inline) kann man unnötige
Kopiervorgänge oft reduzieren, bzw. der Compiler kann das
"wegoptimieren"
Beim gcc gibts da immer die *.lss (oder warens .lst) Dateien in denen
das vom Compiler produzierte Resultat steht.
hat man z.B. die definition "void func1(...)" und findet dann ein "RCALL
func1" im asm wieder, macht man einfach ein "inline" vor "func1" und
compilierts nochmal und hofft dass der Compiler was gescheites draus
macht.
Ich geb zu hier muss man (ich) oft ein wenig rum probieren und oft hiflt
dann auch nur inline assembler.
evtl schaust dir auch das mal an:
http://www.mikrocontroller.net/articles/AVR-GCC-Codeoptimierung
Gruß
Roland
Also ich glaube nicht, daß ASM per se schneller ist, als gut optimierter
C-Code - der ist nämlich häufig besser, als wenn man dieselbe Aufgabe in
ASM löst.
Voraussetzung ist allerdings ein sehr guter Optimierer und natürlich mit
Verstand entworfene Algorithmen.
Wow, danke Jungs, das hilft mir erst mal unheimlich weiter. Werd erst
mal die mir nicht bekannten Begriffe nachschauen ;-).
@ Hugo: Genau so einen Stack will ich benutzen. Wenn ich das richtig
verstanden habe fange ich bei der obersten Schicht an meine z.B. HTTP
Daten zu verpacken und reiche sie dann Stück für Stück nach unten
weiter, bis sie schließlich in den PHY geschrieben und damit versendet
werden. Umgekehrt beim empfangen. Also muss ich dementsprechend den
Sende-/Empfangspuffer groß genug wählen, um alle Daten mit einem Mal
auszulesen/reinzuschreiben und dies jeweils hintereinander weg.
@Roland: Ja, die Pointersache, damit komm ich noch nicht
hundertprozentig klar, aber ich denke das wird noch. Letztendlich ist
Sinn und Zweck der Sache, das eine bestimmte Operation einen Wert im
Speicher verändert und diese durch eine andere Operation genutzt werden,
oder gibts da noch andere Anwendungen? Kann man, sollte man Pointer
global definieren, oder übergibt man sie immer wieder? Also z.B. bei so
einem Puffer könnte es ja Sinn machen, das man ständig darauf zugreifen
kann, oder?
Die Assemblersache schiebe ich erst mal, bin froh, wenns in C klappt und
wenn die Geschwindigkeit ausreichend ist, dann komm ich vielleicht
drumherum. Was bedeutet denn Inline? wird dann nur diese eine Funktion
assembliert?
@Uhu: Ich war bis jetzt in dem Glauben, dass Assembler manche Sachen in
kürzerer Zeit, weil weniger PCs, schafft. Da gabs hier irgendwo auch mal
ne Diskussion um einen ARM, da hatten sich Leute ran gesetzt und das
verglichen, wenn ich nicht irre.
So genug laut gedacht, werd jetzt mal noch n Entwurf machen und morgen
hoffentlich was auf die Reihe bekommen, schönen Abend noch!
Ich schließ mich mal an, es wird von nicht-C Programmierern oder
Windows-C Programmierern total unterschätzt, wie effizient C auf nem MC
sein kann.
Man muß halt ein bischen mitdenken und es dem Compiler nicht unnötig
schwer machen.
Wenn ich Daten über größere Arrays bearbeiten muß, übergebe ich gerne
nur den Pointer auf das Array und dann kann sich die Funktion effizient
daruf austoben.
Wenn man Parameter über mehrere Funktionen übergibt, sollte man nicht
unnötig die Reihenfolge ändern, dann entsteht auch kein unnützer Code.
Funktionsaufrufe, die die eigenen Argumente benötigen, schreibt man
möglichst am Anfang. Funktionen, die den eigenen Returnwert liefern,
möglichst ans Ende. Dann muß wenig zwischengespeichert werden.
Daß 8-Bitter lieber unsigned char, statt int verabeiten, dürfte auch
kein Geheimnis mehr sein.
Peter
Naja, es handelt sich um einen Arm, da relativiert sich das glaub ich.
>Funktionsaufrufe, die die eigenen Argumente benötigen, schreibt man>möglichst am Anfang. Funktionen, die den eigenen Returnwert liefern,>möglichst ans Ende. Dann muß wenig zwischengespeichert werden.
Das verstehe ich nicht so ganz, wieso muss dann weniger
zwischengespeichert werden?
Wenn du schreibst:
return f(x);
erhält die Funktion das Ergebnis von f(x) in einem Register; sie muß das
Ergebnis nicht mehr umkopieren, weil sie es im selben Register
zurückgibt.
Andreas Kaiser wrote:
> Nicht nur das, u.U. wird auch der Funktionsaufruf eingespart und die> aufgerufene Funktion direkt angesprungen (JMP statt CALL+RET).
Nein, das ist nicht üblich, denn die Stackverwaltung wird damit gestört.
Peter Dannegger wrote:
> Wenn ich Daten über größere Arrays bearbeiten muß, übergebe ich gerne> nur den Pointer auf das Array
Was denn auch sonst? Ein Array wird in C immer über den Zeiger auf
das erste Element übergeben, ein Umkopieren gibt es nicht (außer, man
legt vorher explizit manuell eine Kopie an).
@Uhu:Ah, jetzt hat ers, danke.
@Jörg: Ich bin sicher Peter weiß das auch, aber ich wusste das vorher
nicht unbedingt alles, weil ich mich grad erst tiefer mit C beschäftige,
da bin ich auch für Hinweise auf Selbstverständlichkeiten dankbar, die
mich schneller voran bringen.
> Nein, das ist nicht üblich, denn die Stackverwaltung wird damit gestört.
Das ist durchaus üblich, aber natürlich nur wenn dadurch nichts
durcheinander gebracht wird. Und bei registerorientierten Maschinen ist
das nicht so selten. Nennt sich "tail call elimination" und ist in GCC
drin (z.B. ARM).
Uhu Uhuhu wrote:
> Geht aber nur, wenn die aufrufende Funktion keine lokalen Variablen hat> und keine Framepointer auf dem Stack abgelegt werden.
Lokale Variablen darf sie haben, solange sie nicht auf dem Stack liegen
sondern in nicht zu rettenden Registern. Aber lass das ruhig den
Compiler entscheiden, der weiss sowas. Und Framepointer sind heutzutage
in optimiertem Code nicht so häufig anzutreffen, vorausgesetzt der
Stackpointer kann zur Adressierung genutzt werden.
Andreas Kaiser wrote:
> Und Framepointer sind heutzutage in optimiertem Code nicht so häufig> anzutreffen, vorausgesetzt der Stackpointer kann zur Adressierung> genutzt werden.
Da irrst du, denn das Debuggen nicht nur von Dumps wird damit deutlich
einfacher, weshalb viele Windows-Applikationen darauf nicht verzichten.
> Da irrst du, denn das Debuggen nicht nur von Dumps wird damit deutlich> einfacher, weshalb viele Windows-Applikationen darauf nicht verzichten.
In diesem Thread geht es um optimierten zeitkritischen Code. Ich weiss
nicht, ob die Release-Versionen von Windows-Apps mit Framepointer
übersetzt werden, aber wenn man es es auf zeitkritischen Code anlegt,
sollte man darauf verzichten.
Andreas Kaiser wrote:
> Und im Debug-Code ist intensive Optimierung ohnehin wenig hilfreich.
Das stimmt so nicht. Ich habe sehr viel mit Speicherdumps zu tun, die
über Windows Error Reporting zurückkommen. Das sind selbstverständlich
alles Releaseversionen und dann muß eben der Post Mortem Dump analysiert
werden. Dabei sind Stackframes - die übrigens in den MS-Release-Libs
durchweg vorhanden sind, eine deutliche Arbeitserleichterung, zumal der
optimierte Code häufig nicht gerade die Struktur des Quellcodes
abbildet.
>> Und im Debug-Code ist intensive Optimierung ohnehin wenig hilfreich.>> Das stimmt so nicht.
Dann gilt also umgekehrt: In Debug-Code ist Optimierung hilfreich???
Weiter unten schreibst du dann selber:
> zumal der optimierte Code häufig nicht gerade die Struktur des> Quellcodes abbildet.
was ja der Punkt ist, auf den ich mich mit o.A. Aussage bezog. Was also
ist an meinem obigen Satz falsch?
> Ich habe sehr viel mit Speicherdumps zu tun, die> über Windows Error Reporting zurückkommen. Das sind selbstverständlich> alles Releaseversionen und dann muß eben der Post Mortem Dump analysiert> werden.
Hier beziehst du dich auf Release-Code. Mir ist klar, dass Framepointer
für Analyse nützlich sein können. Aber was hat das mit meiner Aussage zu
Debug-Code zu tun?
PS: Evtl. Missverständnis? Debug-Code = Debugversion eines Programms,
Release-Code = Releaseversion.
Ich hab mich mal intensiv mit der Zeigerbenutzung auseinandergesetzt. Im
Kernighan/Ritchie ist das eigentlich auch gut verständlich erklärt.
Allerdings habe ich noch ein Problem:
1
double*dp,atof(char*)
Klar ist, dass der Zeiger dp ein double-Typ ist, also Adressen, auf die
er zeigt auch nur double-Werte enthalten dürfen, oder? atof ist auch vom
Typ double, auch klar. Dann steht aber weiter: "...das Argument von atof
ein Zeiger auf char ist." Was heißt ein Zeiger auf char? Ein Zeiger auf
den Datentyp direkt? Oder muss das Argument ein Zeiger sein, der vom Typ
char ist? Soll atof hier eine Funktion sein, oder einfach irgendeine
Variable?
Noch was, in C muss man doch glaub ich Zeiger wieder dereferenzieren,
also irgendwie löschen, dass der Speicher nicht voll gemüllt wird, gibt
ja keinen GarbageCollector. Wie mach ich das und wann?
Danke übrigens für die bisherigen Antworten, auch aus kleinen
Streitgesprächen kann man viel lernen ;-)
EDIT: Hab ich ganz vergessen ein Zeiger auf void, heißt das einen Zeiger
zu deklarieren, dessen Datentyp erst zur Laufzeit bekannt ist, bzw. dem
dann ein Zeiger mit bestimmtem Datentyp zugewiesen wird?
> double *dp, atof(char *)>> Klar ist, dass der Zeiger dp ein double-Typ ist, also Adressen, auf die> er zeigt auch nur double-Werte enthalten dürfen, oder?
Ja.
> atof ist auch vom Typ double, auch klar.
Der Rückgabetyp, ja.
> Dann steht aber weiter: "...das Argument von atof ein Zeiger auf char> ist." Was heißt ein Zeiger auf char? Ein Zeiger auf den Datentyp> direkt?
So wie dp ein Zeiger auf double ist, ist das Argument ein Zeiger auf
char.
> Oder muss das Argument ein Zeiger sein, der vom Typ char ist?
Der Zeiger ist vom Typ "Zeiger auf char".
> Soll atof hier eine Funktion sein, oder einfach irgendeine> Variable?
Eine Funktion. Obiges ist äquivalent zu:
double *dp;
double atof(char *);
> Noch was, in C muss man doch glaub ich Zeiger wieder dereferenzieren,> also irgendwie löschen,
Dereferenzieren ist was ganz anderes. Dereferenzieren tut man, was man
auf das, worauf er zeigt, zugreift.
> dass der Speicher nicht voll gemüllt wird, gibt ja keinen> GarbageCollector. Wie mach ich das und wann?
Man "löscht" nicht "den Zeiger", sondern gibt dynamisch allokierten
Speicher frei, sofern der Zeiger auf solchen zeigt. Das braucht man
aber, weil der Speicher dynamisch allokiert wurde, nicht weil ein Zeiger
darauf zeigt.
Beispiel:
1
voidf()
2
{
3
doubled=3;
4
double*dp;=&d;
5
}
Hier zeigt dp auf d. d wird automatisch zerstört, wenn die Funktion
beendet ist. Daran ändert auch die Tatsache, daß ein Zeiger darauf
zeigt, nichts.
Wenn du stattdessen nun den Double-Wert dynamisch allokierst, muß er
auch wieder freigegeben werden.
1
voidf()
2
{
3
double*dp=malloc(sizeof*dp);
4
free(dp);
5
}
Hier muß free() aufgerufen werden, weil das, worauf dp zeigt, mit malloc
dynamisch angelegt wurde.
> EDIT: Hab ich ganz vergessen ein Zeiger auf void, heißt das einen> Zeiger zu deklarieren, dessen Datentyp erst zur Laufzeit bekannt ist,> bzw. dem dann ein Zeiger mit bestimmtem Datentyp zugewiesen wird?
void* ist ein Zeiger auf ein Objekt, dessen Typ an der Stelle, wo der
Zeiger definiert wurde, nicht bekannt ist. Es ist im Prinzip eine Art
generischer Zeiger, den man erst wieder in den richtigen Zeigertyp
konvertieren muß, um ihn für irgendetwas benutzen zu können.
> eh die ersten kommen und sagen C und Zeitkritisch beißt sich, ich weiß> Assembler ist schneller.
Ich gehe mal davon aus, dass Du mit "zeitkritisch" jetzt Performace
meinst und nicht auf das Zeit- bzw. Reaktionsverhalten des Prozesses
hinaus willst?
In der Regel ist das ein Trugschluss. Moderne PC-CPUs sind so komplex,
dass Du mit manuell geschriebenem Assembler wahrscheinlich eher bremst
als beschleunigst. Was meinst Du wieviel know-how in den Compilern
steckt, die mit allen Tricks versuchen, optimierten Code zu erzeugen,
der auch tatsaechlich schnell ablaeuft?
Solange Du in C richtig programmierst, kannst Du schon sehr effiziente
Programme schreiben. C ist ziemlich Low-Level und laesst Dir daher viele
Eingriffsmoeglichkeiten -- natuerlich kann man auch viel falsch machen.
Wie wahrscheinlich schon mehrfach gesagt: Wenn Du schnellen Zugriff auf
Daten haben willst, speicher sie irgendwo und arbeite nur mit Pointern.
Wenn Du noch eine spezifische Frage hast lass hoeren ;)
Michael
Icke Muster wrote:
> Ich hab mich mal intensiv mit der Zeigerbenutzung auseinandergesetzt. Im> Kernighan/Ritchie ist das eigentlich auch gut verständlich erklärt.> Allerdings habe ich noch ein Problem:>
1
>double*dp,atof(char*)
2
>
> Klar ist, dass der Zeiger dp ein double-Typ ist,
Nein. dp ist ein Zeiger auf Typ double
> also Adressen, auf die> er zeigt auch nur double-Werte enthalten dürfen, oder?
Die Adressen auf die er zeigt, werden als double interpretiert. Wenn was
anderes dort drinsteht, wird missinterpretiert.
> atof ist auch vom> Typ double, auch klar.
Der Rückgabewert der Funktion atof ist vom Typ double.
> Dann steht aber weiter: "...das Argument von atof> ein Zeiger auf char ist." Was heißt ein Zeiger auf char? Ein Zeiger auf> den Datentyp direkt? Oder muss das Argument ein Zeiger sein, der vom Typ> char ist?
Ein Zeiger auf den Typ char wird als Argument der Funktion atof
übergeben.
> Soll atof hier eine Funktion sein, oder einfach irgendeine> Variable?
Funktion.
> Noch was, in C muss man doch glaub ich Zeiger wieder dereferenzieren,> also irgendwie löschen, dass der Speicher nicht voll gemüllt wird, gibt> ja keinen GarbageCollector. Wie mach ich das und wann?
Fallspezifisch. Wenn du z.B. Speicher dynamisch alloziert hast mit
malloc, gibst du den Speicher mit free wieder frei. Wenn dein Zeiger auf
statischen Speicher zeigt (z.B. auf eine Variable), brauchst du nichts
freizugeben.
> Danke übrigens für die bisherigen Antworten, auch aus kleinen> Streitgesprächen kann man viel lernen ;-)> EDIT: Hab ich ganz vergessen ein Zeiger auf void, heißt das einen Zeiger> zu deklarieren, dessen Datentyp erst zur Laufzeit bekannt ist, bzw. dem> dann ein Zeiger mit bestimmtem Datentyp zugewiesen wird?
Ja, dafür kann man Void-Zeiger benutzen, wobei es sinnvoller ist Zeiger
von bekannten Typen zu benutzen, wenn möglich, weil dann der Compiler
Warnungen schmeissen kann, wenn inkompatible Zeiger vermischt werden.
Jungs, so langsam kommt Licht in den dunklen Wald, hurra.
Nun habe ich gelesen, dass malloc() Speicher vom OS fordert, das heißt,
wenn ich kein OS habe, sondern direkt mit der Hardware arbeite kann ich
diese Funktion nicht benutzen?! Was wäre denn so ein Anwendungsfall für
malloc? Reserviere ich damit Speicher um z.B. einen Puffer anzulegen,
könnte ich dann nicht auch in eine Variable schreiben, über Pointer und
dadurch den gezielten Effekt erreichen? Oder benutzt man das um aus
einem bestimmten Teil des Programms heraus Speicher dauerhaft verfügbar
zu machen, den man vorher nicht anlegen braucht, sondern erst zu diesem
Zeitpunkt anlegt , um ihn dann bei nicht weiterer Verwendung sofort
freigeben zu können?
@Michael:Ja, ich meine Performance. Es geht ja hier um einen ARM(hab ich
das schon mal erwähnt?) und da habe ich bis jetzt immer gehört, dass
zeitkritische Operationen bei Mikrocontrollern wohl in Assembler besser
wären, aber ich lasse mich natürlich gern belehren.
Noch was zu den Rückgabewerten von Funktionen. Ich hab das jetzt so
verstanden, dass dies eigentlich nur Statusmeldungen der Funktion sind.
Also 1-->alles cool, hat geklappt, 0-->versuchs noch mal, es gab nen
Fehler. Die eigentlichen Modofikationen, die ein Fkt. vornimmt werden ja
direkt im jeweiligen Speicher abgelegt. Oder gibt es auch Funktionen
nach dem Prinzip
1
intaddiere(inta,intb)
2
{
3
intc=a+b;
4
returnc;
5
}
? Wobei mir klar ist, dass so eine Operation Zeit frisst, nur zum
Verständnis.
> Nun habe ich gelesen, dass malloc() Speicher vom OS fordert, das heißt,> wenn ich kein OS habe, sondern direkt mit der Hardware arbeite kann ich> diese Funktion nicht benutzen?!
Die Laufzeitumgebungen von Controllern bieten diese Funktion teilweise
auch. Nur würde ich empfehlen, bei Controllerprogrammen den Speicher
soweit wie möglich statisch anzulegen. Gibt eine sehr hässliche
Fehlerquelle weniger.
Die letzte Frage hab ich mir grad selber beantwortet, gibt es. die Frage
ist nur wo besteht der Unterschied, bzw. wann bietet es sich an z.b.
einen Zeiger direkt zurückzugeben.
1
unsignedshortReadFrame_EMAC(void)
2
232{
3
233return(*rptr++);
4
234}
Hier wird der Zeiger um eine Adresse weitergeschoben und die neue
Adresse zurückgegeben. Würde es nicht reichen, wenn ich den Zeiger
verschiebe und dann einfach von anderer Stelle aufrufe, oder kann ich so
die Manipulation besser nachvollziehen und dadurch Fehler besser
ausschließen? Diese Funktion ist Teil eines Keil Webserverbeispiels und
soll einen Frame aus dem Ethernetcontroller lesen, wenn ich das richtig
verstanden hab. Der Pointer wird anfangs so: static unsigned short
*rptr; deklariert. Ihm wird während des Programms die Startdresse des
eingehenden Verkehrs übergeben.
@Andreas: Ich glaube diesen Tip werde ich wohl so umsetzen, zumindest so
lange wie mir der Rest noch nicht 100% klar ist...
> Hier wird der Zeiger um eine Adresse weitergeschoben und die neue> Adresse zurückgegeben.
Nein, es wird der Wert zurückgegeben, auf den der Zeiger vor dem
Weiterschieben zeigte.
Zeiger gibt man genau dann zurück, wenn man die Adresse braucht und den
Wert eben dann, wenn man nur am Wert interessiert ist.
Icke Muster wrote:
> Jungs, so langsam kommt Licht in den dunklen Wald, hurra.> Nun habe ich gelesen, dass malloc() Speicher vom OS fordert,
Ja und nein.
malloc() ist zunächst mal eine Funktion die dir die Laufzeitumgebung
deines C-Systems zur Verfügung stellt. Dein Programm existiert ja
nicht in einem Vakuum, sondern ist in eine (kleine) Laufzeitumgebung
eingebettet, die sich um die Routinearbeiten kümmert. Teil dieser
Umgebung ist auch die Verwaltung von dynamisch allokiertem Speicher.
Natürlich kann sich auch diese Laufzeitumgebung den Speicher nicht
aus den Fingern saugen, sondern muss irgendwo irgendwie zu einem
größeren Speicherblock kommen, den sie dann in die von malloc()
angeforderten Speicherbereiche zerlegen kann. Im Regelfall fodert
daher die Laufzeitumgebung vom Betriebssystem diesen größeren
Speicherblock an.
> das heißt,> wenn ich kein OS habe, sondern direkt mit der Hardware arbeite kann ich> diese Funktion nicht benutzen?!
Nein. Das heist nur, dass es kein Betriebssystem gibt, das man um die
Erlaubnis zur Benutzung eines Speicherblockes fragen muss. Die
Laufzeitumgebung krallt sich dann einfach allen Speicher und
behandelt diesen wie den Speicherblock der von einem OS angefordert
worden wäre.
> Was wäre denn so ein Anwendungsfall für> malloc? Reserviere ich damit Speicher um z.B. einen Puffer anzulegen,
zum Beispiel, ja.
Oder: Stell dir vor du schreibst ein Programm welches Daten
sortieren soll. Um das zu tun, werden die Daten normalerweise
in einem Array gespeichert. Nur: Wie gross dimensionierst du denn
das Array? 100 Einträge, 200?
Egal welche Zahl du nimmst, es wird immer irgendwie falsch sein.
Entweder dimensionierst du zu klein, und kannst damit bestimmte
Datensätze nicht sortieren (weil zu gross) oder du dimensionierst
zu gross und hast damit eine Menge Speicher brachliegen.
> könnte ich dann nicht auch in eine Variable schreiben, über Pointer und> dadurch den gezielten Effekt erreichen? Oder benutzt man das um aus> einem bestimmten Teil des Programms heraus Speicher dauerhaft verfügbar> zu machen, den man vorher nicht anlegen braucht, sondern erst zu diesem> Zeitpunkt anlegt , um ihn dann bei nicht weiterer Verwendung sofort> freigeben zu können?
Man benutzt dynamische Allokierung um damit eine Speicherfläche zu
schaffen, von der man bei der Programmierung nicht weiss bzw. nicht
abschätzen kann, wie gross sie werden wird.
> Noch was zu den Rückgabewerten von Funktionen. Ich hab das jetzt so> verstanden, dass dies eigentlich nur Statusmeldungen der Funktion sind.> Also 1-->alles cool, hat geklappt, 0-->versuchs noch mal, es gab nen> Fehler. Die eigentlichen Modofikationen, die ein Fkt. vornimmt werden ja> direkt im jeweiligen Speicher abgelegt. Oder gibt es auch Funktionen> nach dem Prinzip>
1
>intaddiere(inta,intb)
2
>{
3
>intc=a+b;
4
>returnc;
5
>}
6
>
> ? Wobei mir klar ist, dass so eine Operation Zeit frisst, nur zum> Verständnis.
Wofür du den Returnwert benutzt, ist ganz alleine deine Sache.
Ja, oft benutzt man das für Statusmeldungen und ja oft benutzt
man das um ein Ergebnis einer Funktion zurückzugeben. Und ja,
oft genug wird beides miteinander vermischt. Nimm zb. die bereits
angesprochene Funktion malloc(). Normalerweise liefert die dir
die Adresse des frisch allokierten Speicherbereiches. Konnte kein
Speicher mehr reserviert werden, dann liefert die Funktion 0.
Hier hast du beides in einem: Einen Statuswert (nämlich 0) UND
einen Wert der als Ergebnis her halten muss.
> Nun habe ich gelesen, dass malloc() Speicher vom OS fordert, das heißt,> wenn ich kein OS habe, sondern direkt mit der Hardware arbeite kann ich> diese Funktion nicht benutzen?!
Kommt drauf an, ob sie implementiert ist. malloc() allokiert dynamisch
Speicher. Ob der von einem OS kommt oder von sonstwo, kann dir egal
sein. Wichtig ist nur, ob dein Compiler ein malloc() mitbringt.
> Was wäre denn so ein Anwendungsfall für malloc? Reserviere ich damit> Speicher um z.B. einen Puffer anzulegen, könnte ich dann nicht auch in> eine Variable schreiben, über Pointer und dadurch den gezielten Effekt> erreichen?
Ja, sofern du schon beim Programmieren die Größe für den Puffer kennst.
> Oder benutzt man das um aus einem bestimmten Teil des Programms heraus> Speicher dauerhaft verfügbar zu machen, den man vorher nicht anlegen> braucht, sondern erst zu diesem Zeitpunkt anlegt , um ihn dann bei> nicht weiterer Verwendung sofort freigeben zu können?
Auch, ja.
> @Michael:Ja, ich meine Performance. Es geht ja hier um einen ARM(hab> ich das schon mal erwähnt?) und da habe ich bis jetzt immer gehört,> dass zeitkritische Operationen bei Mikrocontrollern wohl in Assembler> besser wären, aber ich lasse mich natürlich gern belehren.
In manchen Fällen kann sich das lohnen, aber da es eben mit zusätzlicher
Arbeit verbunden ist, sollte man es nur tun, wenn's auch nötig ist und
eine signifikante Verbesserung der Performance überhaupt zu erwarten
ist.
> Wobei mir klar ist, dass so eine Operation Zeit frisst,
Tut sie das?
> unsigned short ReadFrame_EMAC(void)> 232 {> 233 return (*rptr++);> 234 }>> Hier wird der Zeiger um eine Adresse weitergeschoben und die neue> Adresse zurückgegeben.
Nein. Erstens wird nicht die Adresse, sondern der Wert, der dort steht,
zurückgegeben, zweitens ist es nicht die neue, sondern die alte Adresse,
also die vor dem Inkrementieren.
> Würde es nicht reichen, wenn ich den Zeiger verschiebe und dann einfach> von anderer Stelle aufrufe,
Sofern du die Daten über einen globalen Zeiger austauschen willst, ja.
Das tut man aber eher selten. Normalerweise hat der Aufrufer von
ReadFrame_EMAC() keinen Zugriff auf rptr oder sich zumindest nicht dafür
zu interessieren. Das nennt man auch "Kapselung".
> oder kann ich so die Manipulation besser nachvollziehen und dadurch> Fehler besser ausschließen?
Die obige Funktion erlaubt dem Aufrufer gar keine Manipulation, weil sie
keine Adresse zurückgibt.
@ Uhu: Stimmt, auf die nächste Adresse würde ich mit ++*rptr zeigen oder
mit *(rptr++)?, das macht im Context auch deutlich mehr Sinn, immer
diese kleinen Fallstricke...
@Karl Heinz: Stimmt, das Bsp. ist sehr bezeichnend. Tja, darum braucht
man sich halt bei JAVA keine Sorgen machen, aber ich werd mich schon
dran gewöhnen.
Icke Muster wrote:
> @ Uhu: Stimmt, auf die nächste Adresse würde ich mit ++*rptr zeigen, das> macht im Context auch deutlich mehr Sinn, immer diese kleinen> Fallstricke...
Das meinte ich nicht. Du schriebst:
> ... die neue Adresse zurückgegeben.
Das ist definitiv falsch, denn dann sähe der return-Befehl so aus:
return ++rptr;
(Die Klammer um den gesamten Ausdruck braucht man nicht.)
Allerdings hätte sich der Compiler beschwert, wenn du ihn das vorgestzt
hättest:
unsigned short ReadFrame_EMAC(void);
kann garkeinen Pointer zurückgeben.
Icke Muster wrote:
> @Karl Heinz: Stimmt, das Bsp. ist sehr bezeichnend. Tja, darum braucht> man sich halt bei JAVA keine Sorgen machen, aber ich werd mich schon> dran gewöhnen.
(nicht ganz ernst gemeint)
Tja, das ist halt der Unterschied zwischen Jungs und Männern.
Die Jungs veranstalten Chaos in ihren Zimmern und die Mutti
räumt hinterher auf. Die Männer haben sich daran gewöhnt selbst
Ordnung in ihrer Werkstatt zu halten.
Tja, aber der Weg zum Mann ist nun mal nicht der leichteste, wie wir ja
wissen. Aber zumindest hat mein C-Wissen schon die präpubetäre Phase
überwunden, ich werde zum Mann ouh, ouh, ouh...
Uhu Uhuhu wrote:
> Das stimmt so nicht. Ich habe sehr viel mit Speicherdumps zu tun, die> über Windows Error Reporting zurückkommen. Das sind selbstverständlich> alles Releaseversionen und dann muß eben der Post Mortem Dump analysiert> werden.
Die sind aber dann auch auf einem Prozessor entstanden, der für seinen
permanenten Registermangel bekannt ist. Entsprechend kommt man auf
diesem in den meisten Fällen um einen Stackframe sowieso nicht drum
herum, und damit bringt das optionale Weglassen des Stackframes
nichts. Dabei ist es völlig egal, welches Betriebssystem unter der
Anwendung werkelt -- das lässt sich auf meinem FreeBSD genauso
beobachten wie in deinen Windows-Dumps (außer dass FreeBSD auch auf
anderen CPUs läuft, Windows nicht).
Auf einem Richtigen Prozessor[tm] sieht das ganz anders aus. Dort ist
es die Regel, dass die Argumentübergabe an viele Funktionen in
Registern erfolgt, und viele einfache Funktionen kommen mit den per
ABI zugewiesenen Arbeistregistern aus, müssen also nichts zusätzlich
retten. Damit bringt das Weglassen des Stackframes durchaus einiges
an Gewinn. Wenn du weiterhin noch in einer Umgebung arbeitest wie
einem AVR, in der man sowieso keinen post-mortem-Dump anlegen kann,
braucht man auf einen solchen auch keine Rücksicht weiter zu nehmen.
Klar wird das Debuggen (in diesem Falle dann in-circuit) damit
schwieriger, aber es spart eben im Gegenzug Ressourcen, die hier oft
wertvoll sind.
> Die sind aber dann auch auf einem Prozessor entstanden, der für seinen> permanenten Registermangel bekannt ist. Entsprechend kommt man auf> diesem in den meisten Fällen um einen Stackframe sowieso nicht drum> herum, und damit bringt das optionale Weglassen des Stackframes> nichts.
Auch auf diesen Prozessoren kann man den Code ohne Stackframe
compilieren, ohne dafür eine Laufzeit- oder Speicherstrafe zahlen zu
müssen.
Der Preis ist allerdings, daß sich die Stackoffsets dauernd ändern und
eine Dumpanalyse ziemlich mühsam machen, vor allem, wenn man nicht auf
allen Quelltext zugreifen kann.
Daß man auf einen AVR keinen PMD ziehen kann, glaube ich nicht - das
macht das Speicherfenster jedes einigermaßen brauchbaren Debuggers. Du
must nur dafür sorgen, daß die Daten nicht zerschossen werden, bevor du
sie ansehen kannst.
Uhu Uhuhu wrote:
> Auch auf diesen Prozessoren kann man den Code ohne Stackframe> compilieren, ohne dafür eine Laufzeit- oder Speicherstrafe zahlen zu> müssen.
Es bringt dir aber keinen Vorteil mehr. Der Framepointer wird ohnehin
benötigt, um die lokalen Variablen adressieren zu können, da der
Compiler (bis auf sehr, sehr einfache Funktionen) keine freien
Register hat, um lokale Variablen in diesen unterzubringen. Gleiches
gilt für die Funktionsargumente: die werden immer auf dem Stack
übergeben, während das auf ,,richtigen'' Prozessoren die Ausnahme ist
(entweder sehr viele Argumente odere eine unbekannte Anzahl, also
"varargs").
> Daß man auf einen AVR keinen PMD ziehen kann, glaube ich nicht
Du kannst dir den Speicher `live' angucken, wenn du einen Emulator
hast (und wenn die Applikation nicht ohnehin sofort neu gestartet
werden muss, weil ihre Funktion benötigt wird), aber es gibt keinen
"post-mortem dump" im Sinne eines offline abgelegten Speicherabbildes,
mit dem man nachträglich (also nach dem CPU-Reset) noch eine Analyse
durchziehen könnte.
Ich habe auch schon `post mortem'-Analysen auf AVRs gemacht, aber das
ist zugegebenermaßen aufwändiger, als das auf einem IA32-System war.
> @Michael:Ja, ich meine Performance. Es geht ja hier um einen ARM(hab ich> das schon mal erwähnt?) und da habe ich bis jetzt immer gehört, dass> zeitkritische Operationen bei Mikrocontrollern wohl in Assembler besser> wären, aber ich lasse mich natürlich gern belehren.
OK womit wir wieder zeitkritisch mit performant verwechselt haben. Wenn
Du C programmieren kannst und eine Vorstellung davon hast, was hinter
der Fassade passiert, ist das egal. Ich persoenlich finde C absolut
passend fuer einen Mikrocontroller, weil es niedrig genug dafuer ist,
aber komfortabel zu selben Zeit. Dafuer ist C ja gemacht worden,
naemlich um Assembler weitgehend zu ersetzen. Glaube nicht, dass Dich
der Assembler vor Fehlern bewahrt, die kannst Du in jeder "Sprache"
machen.
Ach ja:
1
return(*rptr++);
Sowas ist mistiger Code, da man hier die Auswertungsreihenfolge und
Praezendenz der Operatoren beachten muss. Die Lesbarkeit erhoeht das
nicht wirklich. Die Zeiten, wo sich ein guter Programmierer im Code
profilieren muss nach dem Motto "meinen Code kann nur ich lesen" sind
lange vorbei.
Noch eine Anmerkung zum Thema Performance:
1
intaddiere(inta,intb)
2
{
3
intc=a+b;
4
returnc;
5
}
schreibst Du besser als
1
intaddiere(inta,intb)
2
{
3
return(a+b);
4
}
Wahrscheinlich optimiert Dir das der Compiler sowieso weg aber unnoetige
Operationen sollte man halt vermeiden. Noch performanter waere sicher
folgendes:
1
voidaddiere(inta,intb,int*result)
2
{
3
if(result!=NULL)
4
{
5
*result=a+b;
6
}
7
}
Die Pruefung koenntest Du natuerlich auch weglassen aber das solltest
vllt. eher nicht tun ;)
Und keine Angst mein Jung das kommt alles noch... :D
Michael
> Sowas ist mistiger Code, da man hier die Auswertungsreihenfolge und> Praezendenz der Operatoren beachten muss.
Sowas ist nun wirklich ganz stinknormales C. Wie sieht deiner Ansicht
nach die bessere Alternative aus?
> Noch performanter waere sicher folgendes:>
1
>voidaddiere(inta,intb,int*result)
2
>{
3
>if(result!=NULL)
4
>{
5
>*result=a+b;
6
>}
7
>}
8
>
Im Gegenteil. Das ist viel schlechter als die vorherigen Versionen.
Allein schon deshalb, weil das Resultat nun nicht in einem Register
liegen kann, sondern nun im Speicher liegen muss, und die Optimierung
des Compilers aus mehreren Gründen massiv ausgebremst wird. Und es für
einen sonst oft überflüssigen Stackframe sorgt (was sich bei AVRs massiv
bemerkbar macht).
> Wahrscheinlich optimiert Dir das der Compiler sowieso weg
Mit an Sicherheit grenzender Wahrscheinlichkeit tut er das.
> aber unnoetige Operationen sollte man halt vermeiden.
Wenn sie für den Compiler so leicht zu optimieren sind, besteht dazu
kein wirklicher Grund. In manchen Situationen kann es sogar den Code
kleiner und schneller machen, wenn man solche eigentlich unnötigen
Variablen einführt.
> Noch performanter waere sicher folgendes:>> void addiere(int a, int b, int* result)> {> if (result != NULL)> {> *result = a+b;> }> }
Warum sollte das performanter sein? Man braucht eine zusätzliche Branch
und einen zusätzlichen Parameter. Außerdem zwingst du den Complier, das
Resultat in den Speicher zu schreiben statt effizient per Register zu
übergeben. Das gilt alles natürlich nur, wenn kein inlining betrieben
wird. Mit inlining werden in der Regel beide Varianten gleich schnell
sein, aber dann hat deine Version den Nachteil, daß sie weniger lesbar
und weniger intuitiv in der Benutzung ist.
> Und keine Angst mein Jung das kommt alles noch
Dito ;-)
Wie ein Kollege von mir vor vielen, vielen Jahren sagte:
return ist kein Funktionsaufruf!
Die Klammern hier sind völlig unnötig.
Wenn du schon Klammern benutzt, dann benutze sie um anzuzeigen,
wie du die Auswertereihenfolge haben willst. Also entweder
1
return(*rptr)++;
oder
1
return*(rptr++);
aber einfach nur den Return-Wert einzuklammern bringt nichts. Da
kannst du die Klammern auch weglassen:
1
return*rptr++;
und ersparst den Leser die Fragestellung: "Warum um alles in der Welt
hat er hier Klammern eingesetzt? Wird da die Auswertereihenfolge
umgedreht, oder was?"
> Die Klammern hier sind völlig unnötig.
Klar. Ist aber eine Stilfrage. Nicht wenige Leute schreiben return
grundsätzlich mit Klammern, wahrscheinlich aus Analogie zu if() while()
sizeof() ...
Leute, ihr machts mir nicht gerade einfach ;-). Ist es nun besser wenn
ich das Ergebnis einer Funktion über einen Zeiger zurück gebe, oder
direkt als return? Besser lesbar ist natürlich die zweite Variante, also
wenn das keine Unterschiede bringt würde ich, wo es möglich ist eher die
verwenden. Allerdings lese ich soetwas im Kernighan/Ritchie eher selten,
da wird fast ausschließlich mit Zeigern gewerkelt. Versteht mich bitte
richtig ich will mir nicht gleich am Anfang den Stil versauen ;-).
Übrigens das Codebeispiel, dass ich hier hab ist von 2001, scheinbar gab
es da die Regel meinen Code kann nur ich lesen zu diesem Zeitpunkt noch,
zum Glück ist alles gut dokumentiert. Liegt wahrscheinlich aber auch
daran, dass ich noch nicht so das Auge für manche Techniken habe.
@Michael: Ich dachte halt zeitkritische Abläufe bedingen performanten
Code, vielleicht ist das aber auch ein Verständnisproblem.
Ansonsten wieder mal danke an alle die mich hier fit machen :-)
> Ist es nun besser wenn> ich das Ergebnis einer Funktion über einen Zeiger zurück gebe,
Bei Basisdatentypen wie int/long/double usw. ist ein Pointer für einen
einzigen Returnwert schlicht und einfach Blödsinn. Bei Strukturen als
Returnwert kann es hingegen sinnvoll sein.
> Ist es nun besser wenn ich das Ergebnis einer Funktion über einen> Zeiger zurück gebe, oder direkt als return? Besser lesbar ist> natürlich die zweite Variante, also wenn das keine Unterschiede bringt> würde ich, wo es möglich ist eher die verwenden.
Das würde ich so empfehlen.
> @Michael: Ich dachte halt zeitkritische Abläufe bedingen performanten> Code, vielleicht ist das aber auch ein Verständnisproblem.
Nicht unbedingt. Manchmal muß der Code nicht unbedingt so schnell wie
möglich sein, aber die Ausführungszeit muß sehr genau stimmen, wenn man
z.B. ein Timing einer seriellen Kommunikation einhalten muß oder
Impulszeiten sehr genau messen will. Da hat man in Assembler zumindest
bei so einfachen Architekturen wie AVR die Möglichkeit, die Zeiten auf
den Taktzyklus genau zu bestimmen. In C geht das nicht mehr so einfach,
weil ja aus dem Quellcode alles mögliche werden kann, und dazu hängt es
noch vom Compiler, dessen Version und den Optimierungseinstellungen ab.
Da kann es durchaus auch passieren, daß der Code in C zu schnell wird,
weil der Compiler einem was wegoptimiert.
Michael G. wrote:
> Ich persoenlich finde C absolut> passend fuer einen Mikrocontroller, weil es niedrig genug dafuer ist,> aber komfortabel zu selben Zeit.
Ich pflege C als Highlevelassembler zu bezeichnen.
> Ach ja:>> return (*rptr++);>> Sowas ist mistiger Code, da man hier die Auswertungsreihenfolge und> Praezendenz der Operatoren beachten muss.
Sehe ich nicht so - ich mache das immer so (allerdings ohne die
Klammern), weil es schön übersichtlich ist und Redundanz vermeidet. Zeig
mir die Programmiersprache, bei der Ausführungsreihenfolge und
Operatorpräzedenzen keine Rolle spielen...
Andreas Kaiser wrote:
> Bei Basisdatentypen wie int/long/double usw. ist ein Pointer für einen> einzigen Returnwert schlicht und einfach Blödsinn. Bei Strukturen als> Returnwert kann es hingegen sinnvoll sein.
Auch das kann man so einfach nicht sagen: Wenn man die Adresse braucht,
um die referierte Variable verändern zu können, nützt einem deren Inhalt
herzlich wenig.
Jörg Wunsch wrote:
> Uhu Uhuhu wrote:>>> Auch auf diesen Prozessoren kann man den Code ohne Stackframe>> compilieren, ohne dafür eine Laufzeit- oder Speicherstrafe zahlen zu>> müssen.>> Es bringt dir aber keinen Vorteil mehr.
Es spart genau den Speicher, den die Stacklinks belegen und die Befehle,
sie zu pflegen.
> Der Framepointer wird ohnehin> benötigt, um die lokalen Variablen adressieren zu können, da der> Compiler (bis auf sehr, sehr einfache Funktionen) keine freien> Register hat, um lokale Variablen in diesen unterzubringen.
Nein. Das ebp-Register, das bei Speichermodellen mit Framepointer für
ebendiesen reserviert wird, steht frei zur Verfügung. Lokale Variable
werden über esp indiziert - deswegen ändern sich bei den 32-Bit-Modellen
auch dauernd die Offsets der lokalen Variablen, was die Analyse deutlich
erschwert.
(Die 64-Bit-Modelle wurden in dieser Beziehung etwas cleverer
konstruiert: Dort wird auf den lokalen Variablen beim Funktionseintritt
immer soviel Parameterspeicher allokiert, wie maximal benötigt werden.
=> konstante Offsets.)
> Gleiches> gilt für die Funktionsargumente: die werden immer auf dem Stack> übergeben, während das auf ,,richtigen'' Prozessoren die Ausnahme ist> (entweder sehr viele Argumente odere eine unbekannte Anzahl, also> "varargs").
Also die Idelologie von den ",,richtigen'' Prozessoren" kannst du
getrost in der Rumpelkammer lassen. Die ist ganz gewöhnlicher Blödsinn.
Ich habe von Intel alles von 8 bis 64 Bit schon mit ASM programmiert.
Lediglich bei den 8 und 16-Bit-Prozessoren hätte man sich über eine
handvoll zusätzliche Register und einen gleichförmigeren Befehlssatz
sehr gefreut.
Bei 32-Bit kann man auf mehr Register getrost verzichten. Die
zusätzlichen Adressiermöglichkeiten bügeln die ererbten Mängel
vollständig aus. Zudem ist es wirklich egal, ob du einen Parameter mit
einem mov, oder einem push dorthin schiebst, wo er von der aufzurufenden
Funktion erwartet wird. (Bei 2-3 Cachlevels über dem eigentlichen
Arbeitsspeicher ist das Zeitverhalten auch nicht schlechter, als wenn
alle lokalen Variablen in Prozessorregistern gehalten würden.)
Und wenn du auf einer Vielregistermaschine eine Funktion aufrufst, mußt
du auch entweder die aktuellen Register auf den Stack schieben, oder
hinterher wieder laden.
Mehr spaßeshalber habe ich auch schon den MSP430 mit ASM programmiert
und mich zunächst über die vielen Register gefreut - nur um hinterher
festzustellen, daß ich auch nicht mehr benutzt habe, als auf dem
Pentium.
Ich halte, offen gesagt, diese Glaubenskriege um ,,richtige'' und
,,falsche'' Prozessoren für Streitereien um Kaisers Bart - die überläßt
man besser den Haarespaltern.
> Ich habe auch schon `post mortem'-Analysen auf AVRs gemacht, aber das> ist zugegebenermaßen aufwändiger, als das auf einem IA32-System war.
Was mich beim Itanium genervt hat, sind diese kleinen Fitzelbefehle, die
einzeln wenig tun.
> Zudem ist es wirklich egal, ob du einen Parameter mit einem mov, oder> einem push dorthin schiebst, wo er von der aufzurufenden Funktion> erwartet wird.
Store to load latency bei Integer-Register: typ. 0-1 Takte.
Store to load latency bei Speicher: mind. 3-4 Takte (K8,Core2).
Gleichzeitige Lesezyklen Integer-Register: 9, Speicher: 2.
Doch, das macht sehr wohl einen Unterschied.
Und es machte bisher auch einen Unterschied, ob push oder mov im
Speicher, weil die Resource ESP ausbremst. Kein Problem bei Registern.
Erst ab Core2,K8L ist diese kritische Abhängigkeit weg.
> Und wenn du auf einer Vielregistermaschine eine Funktion aufrufst, mußt> du auch entweder die aktuellen Register auf den Stack schieben, oder> hinterher wieder laden.
Yep, aber es ist nicht unwichtig wann und wo. Entscheidend ist hier
nämlich, dass die unterste Ebene von Funktionen, die üblicherweise die
meistaufgerufene ist, wenig push/pop-traffic hat. Nötige pushs/pops des
callers treten demgegenüber weniger in Erscheinung.
Und grad bei den leaf functions ist auch die oben skizzierte latency
nicht unwichtig. Sollte ein C++ Compiler den "this" pointer auf dem
stack übergeben, wird man das dank dort üblicher sehr kleiner leaf
functions markant zu spüren kriegen.
> Bei 32-Bit kann man auf mehr Register getrost verzichten.
Hast du schon mal mit Befehlen gearbeitet, die eine signifikante latency
aufweisen, und versucht, das Problem mit loop unrolling anzugehen? Ist
ja Standardtechnik für Optimierung. Nur wirst du dann merken, dass man
dabei hässlich viele Register braucht.
Nicht dass das bei AVR,MSP430 irgendwie von Bedeutung wäre. Aber genau
deshalb hat Itanium deren viele.
Worauf ich hinaus will:
- Die Grabenkämpfe zwischen RISC und CISC sind überflüssig
- Die Grabenkämpfe zwischen Verfechtern von Viel- und
Wenigregistermaschinen kann man sich schenken
- Die Grabenkämpfe zwischen Verfechtern unterschiedlicher
Programmiersprachen sind überflüssig.
Das einzige, was in meinen Augen zählt, ist ein schlüssiges
Gesamtkonzept und eine gute Implementierung.
Was zählt, ist die Gesamtleistung eines Systems - Hard- und Software
zusammengenommen.
Vor Urzeiten, als der 8086 noch nicht auf dem Markt war, gab es bereits
die ersten Motorola 68000. Wenn man deren Prozessormodelle und
Befehlssätze vergleicht, kann man einfach nur einen Schluß ziehen:
Bitte, bitte keinen 8086...
Und was hat sich durchgesetzt?
Nicht daß ich das toll fände, aber als glänzendes Beispiel für
ingenieurmäßigen Pragmatismus kann man es allemal nehmen.
> Nicht dass das bei AVR,MSP430 irgendwie von Bedeutung wäre. Aber genau> deshalb hat Itanium deren viele.
Und warum wird des schöne Itanium eingestampft?
Ich kenne die Dinger nur als heulende Heizöfen, die viel zu schwer und
klobig für das sind, was sie bringen.
> Was zählt, ist die Gesamtleistung eines Systems - Hard- und Software> zusammengenommen.
Korrekt.
> Und was hat sich durchgesetzt?
Das hat nichts mit Qualität und viel mit Glück zu tun. Hätte IBM sich
nicht für den 8088 entschieden, wären x86er heute grad so bekannt wie
Z8000.
> Nicht daß ich das toll fände, aber als glänzendes Beispiel für> ingenieurmäßigen Pragmatismus kann man es allemal nehmen.
Naja. Selbst Intel hatte 8086 nur als Übergangslösung bis zum 432
vorgesehen, um 8080-Programme über die Zeit retten zu können. Nur hatten
ebendiese "pragmatischen" Ingenieure mit dem 432 ein grausiges und elend
langsames Monster hingesetzt, und so hatte Intel erstmal nichts besseres
auf Lager.
> Und warum wird des schöne Itanium eingestampft?
Weil x86-Programme nicht wirklich gut darauf laufen. Das, und nur das,
ist das effektive Kriterium im Massenmarkt.
Intel hatte ja selber versucht, den x86 allmählich loszuwerden. Und
dabei AMD nicht beachtet, die mit AMD64 genau in diese Flanke stiessen
und damit x86 ins 64bit Zeitalter retteten. Ohne AMD wäre das Ende von
x86 bereits eingeleitet.
Uhu Uhuhu wrote:
> Also die Idelologie von den ",,richtigen'' Prozessoren" kannst du> getrost in der Rumpelkammer lassen. Die ist ganz gewöhnlicher> Blödsinn.
Du solltest deinen Humorsensor einschalten. Da stehen doch nicht
umsonst Anführungszeichen drumrum.
Trotzdem ist das Konzept z. B. eines UltraSPARC hier deutlich
angenehmer zu benutzen (und einfach genial, da die tatsächliche Anzahl
der Register ein Feature einer aktuellen Implementierung ist, der Code
ist unabhängig davon).
Eine IA32 mit einem AVR zu Vergleichen, das ist eher Äpfel mit
Tomaten...
> Bei 32-Bit kann man auf mehr Register getrost verzichten. Die> zusätzlichen Adressiermöglichkeiten bügeln die ererbten Mängel> vollständig aus.
Sieht irgendwie alles aus, wie immer mehr Knöpfe an die Backe
genäht.
Dass sich technisch Gutes nicht immer durchsetzt, weil der Markt
einfach nicht so funktioniert, wissen wir nicht erst seit dem 8086 und
seit VHS-Video. Das hat eher was mit Marketing, Schmiergeldern, und
den richtigen Partnern beim Golf spielen zu tun...
> Und wenn du auf einer Vielregistermaschine eine Funktion aufrufst,> mußt du auch entweder die aktuellen Register auf den Stack schieben,> oder hinterher wieder laden.
Nö. Guck dir mal eine UltraSPARC an. Da wird nur ein Registerzeiger
um 8 weitergerückt. Speicheroperationen werden erst vonnöten, wenn
die Aufruftiefe die Anzahl der physisch vorhandenen Register
überschreitet. (Das gibt dann einen Trap, und das OS kann
entscheiden, wie viele Registerblöcke jetzt ein- oder ausgelagert
werden.)
> Mehr spaßeshalber habe ich auch schon den MSP430 mit ASM> programmiert und mich zunächst über die vielen Register gefreut -> nur um hinterher festzustellen, daß ich auch nicht mehr benutzt> habe, als auf dem Pentium.
Ich weiß nicht, der GCC hat auf dem AVR keine Probleme, die Register
zu benutzen. ;-) Wie geschrieben, Parameterübergabe über den Stack
ist dort die absolute Ausnahme.
Wir sind aber jetzt hinreichend weit weg vom Ursprungsthema, ich würde
diese Diskussion jetzt von meiner Seite nicht weiter fortführen.
> Nö. Guck dir mal eine UltraSPARC an. Da wird nur ein Registerzeiger> um 8 weitergerückt.
Das ist eigentlich eine Dreiadressmaschine, die alle Operanden in einem
Befehl aus dem Speicher liest und das Ergebnis wieder dort abspeichert.
Alles wird über ein einziges Pointerregister indiziert. Alle anderen
'Register' sind reine Fiktion.
Letztlich ist das nur ein weiteres Beispiel dafür, daß man nicht
unbedingt viele Prozessorregister braucht...
> Das ist eigentlich eine Dreiadressmaschine, die alle Operanden in einem> Befehl aus dem Speicher liest und das Ergebnis wieder dort abspeichert.
Theoretisch richtig, praktisch als Argument absurd, weil in diesem Sinn
jedes Register als Speicherstelle betrachtet werden kann (und in der
Theorie als erste Speicherhierarchie ja auch ist). Komisch zudem dass du
das erst jetzt erwähnst, immerhin arbeitet Itanium auch nicht anders.
Andreas Kaiser wrote:
>> Das ist eigentlich eine Dreiadressmaschine, die alle Operanden in einem>> Befehl aus dem Speicher liest und das Ergebnis wieder dort abspeichert.>> Theoretisch richtig, praktisch als Argument absurd, weil in diesem Sinn> jedes Register als Speicherstelle betrachtet werden kann (und in der> Theorie als erste Speicherhierarchie ja auch ist).
Wieso absurd? Sagte ich nicht, daß in dieser Architektur alle Register
außer dem Pointer-Register Fiktion - d.h. 'gewöhnliche' Speicherzellen -
sind?
Ich schrieb, daß das ein weiteres Argument dafür ist, daß man nicht
einen Haufen Register braucht, sondern im Extremfall eben mit einem
indizierbaren auskommt.
Uhu Uhuhu wrote:
> Das ist eigentlich eine Dreiadressmaschine, die alle Operanden in einem> Befehl aus dem Speicher liest und das Ergebnis wieder dort abspeichert.
Es ist nicht nur eigentlich eine Dreiadressmaschine (aber das trifft
auf alle echten RISCs zu). Und nein, die Register sind dort (bis auf
die spill/chill traps) eben kein Speicher, sondern erheblich
schneller. Darin liegt der Unterschied begraben.
OK, gut für diesen Thread. Tschüss.