DOSferatu hat geschrieben:
Ich sag's mal so: Ich habe noch keine modernen Compiler zerpflückt oder entsprechend erzeugten Code analysiert, um zu sehen, was diese können und wie diese optimieren, um sowohl Rechenpower, Speicher oder beides zu sparen.
Aber ich wage zu bezweifeln, daß sie:
1) komischen Anfänger-Spaghetticode, der sich über 1000 Zeilen erstreckt, entzerren, die hin-/her-Sprünge selbständig entfernen, Subroutinen, die nur 1x im ganzen Programm aufgerufen werden, gleich ins ""Hauptprogramm" einbauen...
2) komische IF-Kaskaden/Sprungsequenzen, die ein mittelmäßiger Programmierer fabriziert hat, automatisch umwandeln würden z.B. in einen einzelnen statusabhängigen Sprungbefehl plus Sprungtabelle (Look-Up-Table).
3) Anstatt Funktions-/Subroutinen-/-Parameter langsamer über den Stack zu übergeben, diese per Register zu übergeben (ja, ich weiß, daß C-Compiler inzwischen auch, wenn man es ANGIBT, manche Variablen über Register lösen)
4) Anstatt für "stackartig" gespeicherte Bytes/Nybbles o.ä. den Stack zu benutzen, stattdessen ein Register als schnellen "Pseudo-Stack" benutzen (wie ich es z.B. mal gemacht habe, für meine über 10 Jahre alte 4-Ebenen-8-Richtungen-Scroll-Levels-Engine)
5) Verschiedene Sub-Bereiche z.B. eines 32bit-Registers als einzelne Adder/Flags zu benutzen, bzw z.B. das gleiche Register als Adder/Subtractor, um Abfragen in Schleifen zu minimieren
6) Sprünge in Schleifen so umgestalten, daß manche nicht mehr gebraucht werden, weil der Sprung gleich durch den normalen Programmablauf von allein passiert (ähnelt Punkt 1) bzw. aufeinanderfolgende Rücksprünge aus mehreren Subroutinen ersetzen durch vorherige Stackmanipulation und Ersetzen von CALLs durch JMPs
7) Direkt auf Bits des Statusregisters reagiert und diese wieder für Operationen "zweckentfremdet", wo es sich anbietet, um so Sprünge zu vermeiden
8) Programmspeicher sparen und gleichzeitig "innere schnelle Schleifen" zu optimieren, indem man selbstmodifizierenden Code benutzt, der sowohl Befehle, Mod-R/M, SIB- Bytes, Präfixbytes und/oder Parameter ersetzt oder durch "Quereinsprünge" aus Befehlen andere Befehle macht, wo es sich anbietet
... und vieles mehr ... (Das sind nur einige Beispiele der leichter verständlichen Dinge. In Assembler/Maschinencode kann man extrem tief abgehen ... je nachdem, wie nötig man es findet.)
All diese - teilweise "schmutzigen" - Dinge können Speicher und Rechenpower sparen, müssen aber von einem Programmierer berücksichtigt werden. Und, wann und wie oft z.B. ein Sprung/Aufruf in einem Programm statistisch gesehen erfolgt, weiß der Programmierer - weil er weiß, welche Art von Daten das Programm am Ende im Schnitt zu erwarten hat. Der Compiler kann es aber nicht wissen.
Du unterschätzt die modernen Compiler ein bisschen...
Zu deinen Punkten:
1) Macht jeder moderne Compiler.
2) Das beherrschen selbst 15 Jahre alte Compiler.
3) Standardmäßig machen das Compiler nicht, vielen bieten das aber als Optimierungsstufe an. Wenn das ein Compiler per default machen würde, wäre der resultierende Code kaum noch zu debuggen.
4) In dem Punkt hast du wahrscheinlich recht. Das machen Compiler meines Wissen nach nicht. So eine Optimierung wäre gar nicht so schwer umzusetzen, allerdings wäre der zu erwartende Performancegewinn für allermeisten Programme gleich 0. Es lohnt sich einfach nicht.
5) Das kann kein Compiler und wird auch nie einer können. Solche Optimierungen lassen sich allerdings in jeder Hochsprache realisieren und sind daher Sache das Programmierers.
6) Der erste Teil: ja, das machen Compiler. Der 2.Teil - nein, das machen sie nicht. Da greift das Argument aus Punkt 3, dass der resultierende Code nicht mehr zu debuggen wäre. Wenn eine Kaskade von Subroutinen so oft aufgerufen wird, dass sich so etwas lohnen würde, dann greifen aber wahrscheinlich anderen Optimierungen, z.B. das Inlining von Funktionen.
7) Da weiß ich ehrlich gesagt gar nicht, was du meinst.

8) Selbstmodifizierender Code ist seit dem 386er eigentlich tot - Stichwort Prefetch Queue. Während ein Befehl ausgeführt wird, werden zeitgleich die nächsten schon geladen. Eine Modifikation hat also keinen Effekt mehr. Ich habe mal selbstmodifizierenden Code für den Pentium gesehen, der vorher aufwändig getestet hat, wie groß die Prefetch-Queue ist und den selbstmodifizierenden Code dann vorher entsprechend modifiziert hat. Ja, wer Spaß auf sowas hat... nur zu!
Wenn ältere Platformen heute noch relevanz hätten, dann wären auch die Compiler soviel besser als früher, dass du es dir 2x überlegen würdest, ob du wirklich alles in Assembler machen möchtest. Beim 386er kann ich es noch nachvollziehen, weil da die entsprechenden Compiler noch eher schwach waren. Der GCC unterstützt den 386er (und den Real Mode insbesondere) schon lange nicht mehr.
DOSferatu hat geschrieben:
Und ja - ich benutze viel mein altes Turbo-Pascal 7 (von Borland), weil es Programmabschnitte gibt, wo eine Umsetzung in Assembler die Zeit und Mühe kaum Wert ist, weil der Speichergewinn und Performancegewinn demgegenüber minimal wäre. Aber gerade z.B. bei Dingen wie Grafikroutinen - gerade, wenn sie z.B. alte "Grafikmodi" irgendwelcher Fremdsysteme emulieren sollen oder vielleicht Sprites mit ihren vielen Parametern... oder vielleicht Digitalsound synthetisieren sollen... da halte ich es durchaus für sinnvoll, die sogenannten "inneren Schleifen" auch gerne mal zu 100% in Assembler umzusetzen und dies entsprechend zu optimieren.
Ich optimiere sowieso nur auf 386er (für meine "besseren"/"größeren" Projekte meine Mindestanforderung, obwohl Optimum dann eher 486er wären), und daher schaue ich wirllich auch nach, wieviele Zyklen welcher Opcode verbraucht oder ob man Geschwindigkeit gewinnt, wenn man Parameter intern in anderer Form vorhält. Wie ein sogenannter "moderner" Compiler überhaupt optimieren kann, ist mir sowieso ein Rätsel. Seit es so viele "Spezialoptionen" gibt, die eine CPU haben kann oder nicht, oder wenn, dann entweder gut oder schlecht supportet, das Gleiche für Grafikkarten usw... Da weiß man nicht, in welche Richtung so ein Compiler überhaupt optimieren will - was auf der einen CPU-Generation vielleicht total performt, schafft auf der anderen ein Worst-Case-Szenario.
(Achja: Ich werde dann immer wieder gefragt: Wieso kein Protected Mode? Naja - zunächst erst einmal ist die CPU in Protected Mode genauso schnell getaktet wie im Real Mode, also ist Protected Mode nicht "automatisch schneller" oder so. Es hängt immer von der Aufgabenstellung ab und wie man programmiert. Und manche Dinge sind in PM z.B. so kompliziert gelöst, daß manche Sachen damit sogar langsamer sind als im RM. Das segmentierte Speichermodell hat nicht nur Nachteile.)
Ich kann deine Leidenschaft, aus einem alten System das Optimum herauszuholen, sehr gut nachvollziehen. Es macht halt Spaß immer noch ein paar Taktzyklen einzusparen und irgendwelche Routinen Schritt für Schritt noch ein bisschen besser zu machen. Nur darf man diegleiche Akribie nicht von anderen erwarten. Für viele hat das Ziel, einfach fertig zu werden, einen höheren Stellenwert. Ich habe auch noch ein Projekt für den 486er in Arbeit und versuche natürlich auch das beste aus dem System herauszuholen. Ich mache das aber alles in C/C++ und schaue mir später an, wo die meiste Rechenzeit verbraten wird. Entsprechende Teile werden dann durch Assembler ersetzt. So haben es ja auch die späteren DOS-Spiele gemacht, die alles aus den Rechner rausgeholt haben. Die haben kaum noch Assembler verwendet, weil die Compiler immer besser wurden. Bei Wolfenstein 3D gab's noch eine Menge davon, bei Doom schon deutlich weniger, und Quake or DukeNukem3D waren dann schon 99% C-Code. Es hat sich einfach nicht mehr gelohnt.
DOSferatu hat geschrieben:
Und ja - es ist natürlich schlimm, daß ich z.B. für meine VGA/SB-Spiele 80386er voraussetze (weil ich zwar Realmode/V86Mode, aber 32bit-Register und die erweiterten Segmentregister benutze) und so läuft es dann nicht auf 80286er, 80186er oder 8086/8088er. Aber wenn ein Emulator, der ein 30 Jahre altes System emulieren soll, schon streikt, wenn er keine 64bit-CPU und kein SSE und keine 3D-fähige Grafikkarte vorfindet - das finde ich dann wirklich schon mehr als arm.
Das liegt aber daran, dass er für das entsprechende System kompiliert wurde und der Programmierer von den Compilereinstellungen keine Ahnung hatte.

Wenn das Programm Open Source ist, ist das ja aber kein Problem VICE z.B. kannst du ohne weiteres für DOS kompilieren und der funktioniert dort wunderbar. Dass er auf einem 486er deutlich langsamer ist als ältere Emulatoren, die ich von früher kenne (z.B. C64S), liegt nur daran, dass VICE einfach viel genauer in der Emulation ist. Den Sourcecode habe ich mir auch schon oft angeschaut - das ist guter C-Code. Man könnte sich die Mühe machen und mal mit einem Profiler schauen, wo die Rechenzeit verbraten wird und entsprechende Routinen in Assembler umschreiben. Da holst du vielleicht 5% raus. Alles Weitere lohnt nicht mehr.
DOSferatu hat geschrieben:
Aber ich wäre eben nicht stolz darauf und würde mich nicht mehr (Hobby-)Programmierer nennen, wenn ich mit irgend einem Game-Maker und vorgefertigten Tiles, Images, Effekten usw. ein 2D-Spiel zusammengeschlunzt hätte, was dann eine 1 GHz-Maschine mit 3D-Karte braucht, um überhaupt zu starten. Mir wäre das irgendwie peinlich...
Ich wäre auch nicht stolz drauf, aber hey - wo soll der arme Programmierer eine Maschine mit weniger als 1 Ghz und ohne eingebaute 3D-Fähigkeiten herbekommen?

Jeder hat irgendwie so ein Ding auf dem Tisch stehen. Daher läuft der Müll auch überall. Ich bin da generell auch sehr kritisch. Und wenn jemand sagt, dass er in Javascript programmiert, dann bin ich der erste, der ihm erklärt, dass das mit programmieren wenig zu tun hat, was er da macht...

Aber die Welt dreht sich weiter und wenn du möchtest, dass die Menschen deine Spiele spielen, dann wirst du sie auch mit einer DosBox bündeln müssen. Und kaum ein Spieler weiß, was da im Hintergrund eigentlich passiert. Aber du kannst dich stolz schätzen, dass man dein Spiel auch noch in 50 Jahren spielen kann - während der Hobbyprogrammierer mit seinem Game-Maker sein Spiel vermutlich schon in 10 Jahren nirgends mehr starten kann. Ich habe schon als Kind Mitte der 80er Spiele für den C16 geschrieben - die laufen heute noch. Ein kommerzielles Spiel, dass ich Anfang der 2000er für Win98/2000/XP geschrieben habe, startet unter Windows 7 nur noch mit einem Patch und unter Windows 10 gar nicht mehr, obwohl es blitzsauber programmiert wurde. Das hat schon eine gewisse Ironie. Microsoft hat in dem Fall ein paar Direct3D-Funktionen aus Windows einfach rausgeschmissen. Dumm gelaufen...
DOSferatu hat geschrieben:
Ach ja, zum Thema C noch mal:
Ich weiß nicht, was die Leute alle so daran mögen - die Sprache wirkt auf mich unaufgeräumt und zusammengeschustert. Was da als Syntax bezeichnet wird, wirkt wie ein zusammengeworfener Haufen von mindestens 5 verschiedenen Syntaxkonzepten. Bei Variablentypen ist nicht einmal festgelegt, welche Bitbreite sie haben und das Stringhandling ist das Letzte... (Nicht daß Strings jetzt wirklich wichtig wären. Aber sie sind eben beliebt...) - Aber das muß natürlich jeder selbst wissen -über Hochsprachen braucht man sich nicht zu streiten, es gibt sicher Gründe, wieso es so viele davon gibt. Da hat jeder andere Präferenzen.
Und ja, das war wahrscheinlich wieder etwas mehr Text als nötig.
Deine Kritik an C ist absolut berechtigt, nur bedenke: die Syntax von C ist aus den frühen 70ern! Niemand konnte damals ahnen, wohin die IT-Welt einmal geht und wie komplex Software einmal werden wird. Unter der Voraussetzung ist es schon fast erstaunlich, dass man heute noch einigermaßen vernünftig in C entwickeln kann. Und auch C hat sich weiterentwickelt. Heute gibt es Datentypen wie uint8_t oder int16_t, die die Anzahl der Bits garantieren. Und C wird heute oft mit C++ kombiniert, dessen String-Handling eindeutig besser ist. C hat den großen Vorteil, dass es Compiler für nahezu jede Platform gibt - und wenn du etwas in C schreibst, kannst du dir sicher sein, dass du es auch noch in 10 Jahren auf einem neuen System kompilieren kannst. Bei den meisten anderen Hochsprachen ist fraglich, ob die die nächsten 10 Jahre überleben. Wobei nicht einmal klar ist, ob man C zu den Hochsprachen zählen sollte. Für viele ist es nur ein besserer Makro-Assembler. Es gibt z.B. auch heute so nette Makros wie __builtin_expect, mit denen du bei If-Abfragen sagen kannst, welcher Fall der wahrscheinlichere ist und der Compiler optimiert dann entsprechend.