GMBigB hat geschrieben:Die heutigen Compiler sind schon sehr gut. Auf einer x86-Platform wirst du für einfache Programme, dazu zähle ich mal den erwähnten Sudoku-Löser, große Schwierigkeiten haben mit Assembler-Code einen C-Compiler zu schlagen.
Ja, wie erwähnt - den Roman kenn ich schon auswendig. Man muß dazu trotzdem sagen, daß ein Compiler nur das machen kann, was der Programmierer eingibt.
GMBigB hat geschrieben:Das liegt daran, dass die Prozessoren sehr komplex geworden sind, und Compiler so etwas wie Pipelining, Branchprediction oder Cache Hits berücksichtigen.
Solange Dinge wie diese vorhanden sind. Das bedeutet, daß so etwas erst einmal vorausgesetzt sein muß.
Also: Wenn es auf einem 386er oder 486er laufen soll (siehe Thread-Topic: Es ging um Emulation alter Systeme), dann kann man von solchen Dingen schon wieder nicht ausgehen. Und meiner Meinung nach reicht ein mittelguter 486er dicke aus, um alte 8bit und frühe 16bit Konsolen gescheit und spielbar zu emulieren (wenn auch vielleicht nicht mit 100% voller Framerate).
GMBigB hat geschrieben:Wenn du auf einem modernen Prozessor in Assembler so programmierst, wie du es von einem 6502 oder 68000er gewohnt bist, dann stellt ein Compiler deine Arbeit tatsächlich locker in den Schatten.
Das mag sein - aber das ist ja genau das Thema hier: "Zu hohe Hardwareanforderungen..."
Auf einer 4 GHz Octocore Maschine mit 1 TB RAM oder so kann wahrscheinlich jedes noch so dämliche Skriptkiddie ein ruckeliges Tetris oder TicTacToe hinzaubern... Aber es geht ja hier darum, nur die Systemleistung/-ressourcen zu benutzen, die wirklich nötig sind - und eben NICHT mit überkandidelten "Standheizungen" Dinge zu tun, die Leute in den 80ern mit Systemen geschafft haben, die nicht mal eine Passivkühlung brauchten...
GMBigB hat geschrieben:Natürlich schlägt der beste Assembler-Code auch heute noch C-Code, aber der Abstand ist doch sehr gering. Hier sei Stockfish erwähnt, das aktuell beste Schachprogramm. Jemand hat sich vor ein paar Jahren mal die Mühe gemacht und die Engine komplett in Assembler programmiert. Ergebnis: 25% schneller. Das ist ungefähr auch das, was man auch in anderen Bereichen, in denen Assembler noch eine Rolle spielt (z.B. Videodekodierung), rausholen kann.
25% ist aber schon eine ziemliche Hausnummer!
Und: Alleine die "systemübergreifende" Programmierung verhindert, daß man sich direkt auf die Maschine einläßt. Manche Programme/Algorithmen sind für bestimmte CPU-Architekturen super geeignet und erzeugen auf der anderen ein Worst-Case-Szenario. Daran kann auch ein Cross-Compiler nicht wirklich etwas ändern - weil er ja im Kern immer noch das umsetzen soll, was der Programmierer da eingegeben hat. Wenn man aber um die Stärken und Schwächen des jeweiligen Systems weiß, programmiert man gleich ganz anders. (Das ist wie beim Autofahren: Wer weiß, wie die Kiste funktioniert, fährt mit weniger Systemverschleiß. Thema "Wer laut schaltet, braucht nicht hupen"...)
Und ja, das klingt so wie "keine systemübergreifende Programmierung, sondern für jedes Hostsystem getrennt entwickeln" (zumindest die "Inneren Schleifen"). Und ja, genau das - und nur das! - bringt die Performance mit, von der ich rede. Ja, das ist mehr Aufwand. Aber es geht ja in diesem Thread um dieses Thema.
Systemferne Programmierung (ohne Kenntnis/Berücksichtigung des Hostsystems) wird niemals an die Performance heranreichen, die systembezogene Programmierung erreichen kann.
GMBigB hat geschrieben: [...]moderne Prozessorfeatures nutzen [...] SSE oder SSE2 [...] 128 Bit-Register [...]
Zählt alles zu "zu hohe Hardwareanforderungen".
GMBigB hat geschrieben:Dass Softwareentwickler heute nicht mehr schlanke und resourcenschonende Programme schreiben können, liegt daran, dass sie es nie gelernt haben - ganz einfach weil diese Fähigkeit heute kaum noch gebraucht wird.
Mag sein. Und das ist auch der Grund für "zu hohe Hardwareanforderungen" für quasi alles und jedes.
Und ja - genau solche Sachen sind die Gründe für viele "schlimme Software".
Bei "Programmierern", die an die Benutzung untypisierter Variablen (ich sag nur: "VARIANT") gewöhnt sind (die größte Sch**** unter der Sonne!)... da braucht man sich nicht zu wundern, wieso alles so speicherfressend und lahm ist. Bei untypisierten Variablen muß bei jedem Zugriff (egal ob bei Zuweisungen, ob sie in einer Formel auftreten oder ein Parameter sind) geprüft werden, was sie "denn heute gerade wieder sind": Fließkomma/Integer? Vorzeichen oder nicht? Vielleicht ein String? (ASCII? Unicode? Irgendwas proprietäres?) Eine Menge? Ein Feld aus all dem? Ein verschachteltes Feld? Ein Record? Gar ein Pointer? Ein Pointer auf einen Pointer?
Dann den Wert herauskratzen, umwandeln, wo möglich. Fehler generieren, wenn unmöglich - ODER (was noch öfter benutzt wird), "fehlertolerant" auf irgendetwas plausibles setzen... und das bei jedem Zugriff. Falls jemand ein ... naja, Idiot ist (wie soll ich es anders ausdrücken?), benutzt er aus Bequemlichkeit und weil er es nicht anders gelernt hat, weil er es aus (Web-)Skripts kennt oder was weiß ich, so VARIANT-Variablen für alles und jedes - also auch für Schleifenvariablen und wasweißich. Achja - und natürlich ist die Wertebereichsprüfung auch die ganze Zeit angeschaltet - wenn man zu dusslig ist, wenigstens seine internen, selbst gesetzten und geänderten Variablen durch Programmierung im plausiblen Wertebereich zu lassen, muß das bei jedem Zugriff durch eine Bilbiothek geprüft werden... und so weiter... Und das Ganze wird dann natürlich nicht compiliert - ist ja viel praktischer, wenn man's im Entwicklungssystem jeweils live sehen kann, also ist da ein Interpreter drunter, der selbst ebenfalls nicht auf dem nativen System, sondern auf 'ner VM läuft, die in irgend einem Web-Dialekt-Skript geschrieben ist...
Wenn wenn so jemand ein Programm (Spiel, Anwendung, wasauchimmer) baut und die läuft auf der aktuellsten 4 GHz Octocore 1TB RAM Atomrakete von Computer noch gerade so einigermaßen ruckelfrei (auch wenn sein Tic-Tac-Toe in Multitask-Systemen jeweils 95% der Rechenleistung für sich beansprucht...) --- und ihm dafür dann auch noch jeder auf die Schulter klopft: "Haste fein gemacht"... bzw. sowas dann schon als "IT-Experte" zählt (oder er sich zumindest so fühlt)... Dann wird er nie erfahren, was er eigentlich im Computer "damit anrichtet" oder wieso die Raumtemperatur jedesmal steigt wenn jemand sein Spiel spielt.
Und dann lernt der auch nie vernünftig programmieren.
GMBigB hat geschrieben:Ich habe selbst schon bei Projekten mitgearbeitet, wo man lieber zwei Dutzend Server gekauft hat, obwohl mit Optimierung des Codes und Umstieg auf eine andere Programmiersprache ein einziger Server ausgereicht hätte. Und so haben zwei Dutzend Server rund um die Uhr Javascript-Code interpretiert - ganz einfach weil es günstiger so war. Wem will man diese Entscheidung verübeln?
Niemand verübelt hier irgend etwas. Aber in diesem Thread geht es eben genau darum - daß heutzutage lieber "mit Kanonen auf Spatzen geschossen wird", anstatt etwas mehr Mühe in die Software zu stecken.
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.
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.)
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.
Aber ich will meine 386er-Voraussetzung hier keinesfalls schönreden - es ist schlimm, und für 2D-Arcade muß meiner Meinung nach auch weniger ausreichen! Aber wenn man das (wie ich) einsieht, kann man auch an sich arbeiten. Vielleicht hätten auf 286er meine Sprites keine Option für Halbtransparenz mehr oder sich in 256 Stufen drehen zu können - möglicherweise auch nicht einmal mehr Skalierung. Aber nicht jedes Spiel benötigt diese Optionen - also könnte man hier auch tiefer ansetzen.
Ich bin aber auch nur eine einzelne Person - habe einen Job und das bißchen Freizeit nach der Arbeit (wenn man müde ist) und am Wochenende (wo es auch nicht immer besser ist) ist alles, was ich habe. Und im Gegensatz zu anderen (die z.B. Emulatoren bauen) habe ich auch kein "Team", sondern entwickle den ganzen Kram alleine - und ohne Benutzung fremder Bibliotheken. Deshalb bin ich natürlich SEHR langsam.
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...
Irgendwie habe ich auch die Erfahrung gemacht (ist natürlich rein subjektiv, jeder hat seine eigenen Erfahrungen)... daß, je mehr "Klicki-Bunti" ein Programm/Emulator wasauchimmer ist, umso weniger performt er. Also: Die Dinger(Emulatoren), die selbst mit bunten hochauflösenden mausgestützten Menüs daherkommen, sind zwar "ringsherum" nett bedienbar, aber bei ihrer eigentlichen Aufgabe, nämlich die besagten Systeme zu emulieren, performen sie nicht richtig, können manche Modi nicht richtig darstellen, oder setzen schon bestimmte - langsame - APIs voraus, um manches zu tun und alles ist eher so "Hauptsache sieht von außen gut aus und geht irgendwie".
Und demgegenüber stehen die von außen eher "häßlichen" Emulatoren, deren Menüs sich auf das Notwendige beschränken (Textmode, evtl ohne Maus), teilweise sogar nur per Kommandozeile gestartet... Aber die stammen dann oft von der NICHT "Windows-/MacOS-verspielten" Gilde, sondern von irgendwekchen maschinennah agierenden Cracks, denen es eher um die performante Emulation des Systems geht (als um das "KlickiBunti" außerhalb) und die dies vollständig und performant tun.
Das gleiche bestätigt auch mein Kumpel, der z.B. oft Dinge tut wie Videos und/oder Sound encoden/re-encoden. Der sagt das gleiche: Die "schicken" GUI-basierten Dinger benutzen intern irgendwelches generalisiertes Zeug, kommen nicht mit irgendwelchen komischen Formaten klar und sind zudem langsam. Und die Kommandozeilen-Tools, die mit kryptischen Parametern daherkommen (allerdings wäre es ein Kinderspiel, dafür ein kleines Frontend zu bauen), benutzen alles, was das System eventuell bietet (und funktionieren auch noch, wenn das System weniger bietet!), verstehen jedes Format und sind außerdem schnell.
Mir ist natürlich klar, daß man nicht für "jedes und alles" in die tiefste Systemebene kriechen muß und oft kann ein schnell mal hingeschlunztes Skript mal schnell ein kleines Problem lösen - und zwar schneller, als wenn man sich in die Untiefen der Bits der Maschinencodeebene stürzt und Stunden mit Debugging verbringt, in denen man das eigentliche Problem schon lange manuell gelöst hätte.
Aber bei Emulatoren - also Programmen, die komplett andere - teilweise zu 100% zum Hostsystem inkompatible - Systeme - emulieren sollen, und außer der fremden CPU auch noch Sound-/Grafik-Chips mit all ihren seltsamen Modi oder altmodischem Speicherbanking und wasnichtalles... Da sollte man so viel Mühe wie nur irgend möglich in die Performance reinstecken. Denn das ist ja NICHT nur ein "kleines Problem, das mal nebenher gelöst werden soll", das ist ja ein Programm, das wirklich längerfristig und dauerhaft genutzt werden soll und das nur wirklich nützlich ist, wenn es seinen Zweck auch zur Zufriedenheit erfüllt.
Ab und zu spiele ich so von Leuten (ausm Internet, kenn die nicht persönlich) "selbstgemachte" Spiele, die teilweise mit "Game-Makern", teilweise auch "händisch" gemacht wurden - und auch, wenn einige davon schon Spaß machen, bin ich gelegentlich regelrecht erschüttert/entsetzt darüber, welche Systemparameter manche Leute heutzutage voraussetzen für Dinge wie ein 2D-Arcade-Spiel, damit es ruckelfrei läuft - bzw. überhaupt spielbar ist bzw. überhaupt startet.
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.