paint-brush
Die wahren C++-Killer (Nicht du, Rust)von@oleksandrkaleniuk
50,726 Lesungen
50,726 Lesungen

Die wahren C++-Killer (Nicht du, Rust)

von Oleksandr Kaleniuk17m2023/02/14
Read on Terminal Reader
Read this story w/o Javascript

Zu lang; Lesen

Guten Code mit schlechten Programmierern zu schreiben ist ein Problem des 20. Jahrhunderts. Jetzt brauchen wir noch besseren Code, der jedoch von guten Programmierern geschrieben wurde, eine Aufgabe, die kein aktueller C++-Killer angeht. Die wahre Revolution liegt jenseits der Compiler.

People Mentioned

Mention Thumbnail
featured image - Die wahren C++-Killer (Nicht du, Rust)
Oleksandr Kaleniuk HackerNoon profile picture


Hallo! Ich bin Oleksandr Kaleniuk und ein C++-Holic. Ich schreibe seit 17 Jahren in C++ und habe in all diesen 17 Jahren versucht, diese verheerende Sucht loszuwerden.


Alles begann im Jahr 2005 mit einer 3D-Weltraumsimulator-Engine. Die Engine hatte alles, was C++ im Jahr 2005 hatte: Drei-Sterne-Zeiger, acht Abhängigkeitsebenen und überall Makros im C-Stil. Es gab auch Montageteile. Iteratoren im Stepanov-Stil und Metacode im Alexandrescu-Stil. Der Code hatte alles. Außer natürlich der Antwort auf die wichtigste Frage: Warum?


Nach einiger Zeit wurde sogar diese Frage beantwortet. Nur nicht als „wozu“, sondern eher als „wie kommt es“. Wie sich herausstellte, wurde die Engine etwa acht Jahre lang von fünf verschiedenen Teams geschrieben. Und jedes Team brachte seine Lieblingsmode in das Projekt ein, indem es den alten Code in frisch gestaltete Wrapper verpackte und dabei nur etwa 10–20 Mikrocarmacks an Funktionalität hinzufügte.


Zuerst habe ich ehrlich versucht, jede Kleinigkeit zu verstehen. Das war überhaupt keine erfreuliche Erfahrung und irgendwann habe ich aufgegeben. Ich war immer noch damit beschäftigt, Aufgaben zu schließen und Fehler zu beheben. Ich kann nicht sagen, dass ich sehr produktiv war, aber genug, um nicht gefeuert zu werden. Aber dann fragte mich mein Chef: „Möchten Sie einen Teil des Shader-Codes von Assembly nach GLSG umschreiben?“ Ich dachte Gott weiß, wie dieses GLSL aussieht, aber es könnte unmöglich schlimmer als C++ sein und sagte ja. Es war nicht schlimmer.


Und so wurde es zu einem Muster. Ich habe immer noch hauptsächlich in C++ geschrieben, aber jedes Mal fragte mich jemand: „Möchtest du das Nicht-C++-Ding machen?“ Ich war sicher!" und ich habe das getan, was auch immer es war. Ich habe in C89, MASM32, C#, PHP, Delphi, ActionScript, JavaScript, Erlang, Python, Haskell, D, Rust und sogar in der unverschämt schlechten InstallShield-Skriptsprache geschrieben. Ich habe in VisualBasic, in Bash und in einigen proprietären Sprachen geschrieben, über die ich rechtlich nicht einmal sprechen kann. Ich habe sogar aus Versehen selbst eines gemacht. Ich habe einen einfachen Interpreter im Lisp-Stil erstellt, um Spieleentwicklern dabei zu helfen, das Laden von Ressourcen zu automatisieren, und bin in den Urlaub gefahren. Als ich zurück war, schrieben sie die gesamten Spielszenen in diesem Interpreter, also mussten wir ihn mindestens bis zum Ende des Projekts unterstützen.


In den letzten 17 Jahren habe ich ernsthaft versucht, mit C++ aufzuhören, aber jedes Mal, wenn ich etwas Neues ausprobiert habe, bin ich zurückgekommen. Dennoch halte ich das Schreiben in C++ für eine schlechte Angewohnheit. Es ist unsicher, nicht so effektiv, wie man es sich vorstellt, und es verschwendet einen großen Teil der geistigen Leistungsfähigkeit eines Programmierers für Dinge, die nichts mit der Erstellung von Software zu tun haben. Wussten Sie, dass in MSVC uint16_t(50000) + uin16_t(50000) == -1794967296 ? Weißt du, warum? Ja das ist, was ich dachte.


Ich glaube, dass es die moralische Verantwortung langjähriger C++-Programmierer ist, die junge Generation davon abzuhalten, C++ zu ihrem Beruf zu machen, genauso wie es die moralische Verantwortung von Alkoholikern ist, die nicht aufhören können, die Jugend vor der Gefahr zu warnen.


Aber warum kann ich nicht aufhören? Was ist los? Tatsache ist, dass keine der Sprachen, insbesondere die sogenannten „C++-Killer“, in der modernen Welt einen wirklichen Vorteil gegenüber C++ bieten. Bei all diesen neuen Sprachen geht es hauptsächlich darum, einen Programmierer zu seinem eigenen Wohl an der Leine zu halten. Das ist in Ordnung, außer dass das Schreiben von gutem Code mit schlechten Programmierern ein Problem des 20. Jahrhunderts ist, als die Anzahl der Transistoren alle 18 Monate verdoppelte und die Zahl der Programmierer alle fünf Jahre verdoppelte.


Wir leben im Jahr 2023. Wir haben mehr erfahrene Programmierer auf der Welt als je zuvor in der Geschichte. Und wir brauchen heute mehr denn je effiziente Software.


Im 20. Jahrhundert waren die Dinge einfacher. Sie haben eine Idee, packen sie in eine Benutzeroberfläche und verkaufen sie als Desktop-Produkt. Ist es langsam? Wen interessiert das! In achtzehn Monaten werden Desktops ohnehin doppelt so schnell sein. Was zählt, ist, in den Markt einzusteigen, mit dem Verkauf von Funktionen zu beginnen, und zwar möglichst ohne Fehler. In diesem Klima ist es sicher gut, wenn ein Compiler Programmierer davon abhält, Fehler zu machen! Weil Fehler kein Geld einbringen und Sie Ihre Programmierer ohnehin bezahlen müssen, unabhängig davon, ob sie Funktionen oder Fehler hinzufügen.


Jetzt sind die Dinge anders. Sie haben eine Idee, packen sie in einen Docker-Container und führen sie in einer Cloud aus. Jetzt erhalten Sie Ihre Einnahmen von Leuten, die Ihre Software ausführen, wenn dadurch ihre Probleme behoben werden. Selbst wenn es eine Sache tut, es aber richtig macht, werden Sie bezahlt. Sie müssen Ihr Produkt nicht mit erfundenen Funktionen vollstopfen, nur um eine neue Version davon zu verkaufen. Auf der anderen Seite sind nun Sie selbst derjenige, der für die Ineffektivität Ihres Codes bezahlt. Jede suboptimale Routine wird in Ihrer AWS-Rechnung angezeigt.


Im neuen Klima benötigen Sie also weniger Funktionen, aber eine bessere Leistung für alles, was Sie haben.


Und plötzlich stellt sich heraus, dass alle „C++-Killer“, selbst diejenigen, die ich von ganzem Herzen liebe und respektiere wie Rust, Julia und D, das Problem des 21. Jahrhunderts nicht angehen. Sie stecken immer noch im XX fest. Sie helfen Ihnen zwar dabei, mehr Funktionen mit weniger Fehlern zu schreiben, aber sie sind keine große Hilfe, wenn Sie den allerletzten Flop aus der von Ihnen gemieteten Hardware herausholen müssen.


Sie verschaffen Ihnen einfach keinen Wettbewerbsvorteil gegenüber C++. Oder sogar übereinander. Die meisten von ihnen, zum Beispiel Rust, Julia und Cland, teilen sich sogar das gleiche Backend. Sie können kein Autorennen gewinnen, wenn Sie alle das gleiche Auto teilen.


Welche Technologien verschaffen Ihnen also einen Wettbewerbsvorteil gegenüber C++ oder, allgemein gesprochen, gegenüber allen herkömmlichen Compilern, die der Zeit voraus sind? Gute Frage. Ich bin froh, dass du gefragt hast.


C++-Killer Nummer 1. Spirale

Aber bevor wir uns Spiral selbst zuwenden, schauen wir uns an, wie gut Ihre Intuition funktioniert. Was ist Ihrer Meinung nach schneller: eine Standard-C++-Sinusfunktion oder ein 4-teiliges Polynommodell eines Sinus?


 auto y = std::sin(x); // vs. y = -0.000182690409228785*x*x*x*x*x*x*x +0.00830460224186793*x*x*x*x*x -0.166651012143690*x*x*x +x;


Nächste Frage. Was funktioniert schneller, logische Operationen mit Kurzschlüssen zu verwenden oder einen Compiler auszutricksen, um dies zu vermeiden und den logischen Ausdruck in großen Mengen zu berechnen?


 if (xs[i] == 1 && xs[i+1] == 1 && xs[i+2] == 1 && xs[i+3] == 1) // xs are bools stored as ints // vs. inline int sq(int x) { return x*x; } if(sq(xs[i] - 1) + sq(xs[i+1] - 1) + sq(xs[i+2] - 1) + sq(xs[i+3] - 1) == 0)


Und einer mehr. Was sortiert Tripletts schneller: eine Swap-Sortierung oder eine Index-Sortierung?


 if(s[0] > s[1]) swap(s[0], s[1]); if(s[1] > s[2]) swap(s[1], s[2]); if(s[0] > s[1]) swap(s[0], s[1]); // vs. const auto a = s[0]; const auto b = s[1]; const auto c = s[2]; s[int(a > b) + int(a > c)] = a; s[int(b >= a) + int(b > c)] = b; s[int(c >= a) + int(c >= b)] = c;


Wenn Sie alle Fragen entschieden und ohne nachzudenken oder zu googeln beantwortet haben, dann hat Ihre Intuition Sie im Stich gelassen. Du hast die Falle nicht gesehen. Auf keine dieser Fragen gibt es ohne Kontext eine eindeutige Antwort.


Auf welche CPU oder GPU zielt der Code ab? Welcher Compiler soll den Code erstellen? Welche Compileroptimierungen sind aktiviert und welche deaktiviert? Sie können mit der Vorhersage erst beginnen, wenn Sie das alles wissen, oder noch besser, wenn Sie die Laufzeit für jede einzelne Lösung messen.


  1. Ein Polynommodell ist dreimal schneller als der Standard-Sinus, wenn es mit clang 11 mit -O2 -march=native erstellt und auf Intel Core i7-9700F ausgeführt wird. Aber wenn es mit nvcc mit --use-fast-math und auf einer GPU, nämlich GeForce GTX 1050 Ti Mobile , gebaut wird, ist der Standard-Sinus zehnmal schneller als das Modell.


  2. Auch auf i7 macht es Sinn, kurzgeschlossene Logik gegen vektorisierte Arithmetik einzutauschen. Dadurch funktioniert das Snippet doppelt so schnell. Aber auf ARMv7 mit dem gleichen Clang und -O2 ist die Standardlogik 25 % schneller als die Mikrooptimierung.


  3. Und mit Index-Sortierung vs. Swap-Sortierung ist die Index-Sortierung auf Intel dreimal schneller und die Swap-Sortierung auf GeForce dreimal schneller .


Die lieben Mikrooptimierungen, die wir alle so lieben, können unseren Code also sowohl um den Faktor 3 beschleunigen als auch um 90 % verlangsamen. Es hängt alles vom Kontext ab. Wie wunderbar wäre es, wenn ein Compiler die beste Alternative für uns auswählen könnte, sodass sich beispielsweise die Index-Sortierung auf wundersame Weise in Swap-Sortierung verwandeln würde, wenn wir das Build-Ziel wechseln. Aber es konnte nicht.


  1. Selbst wenn wir dem Compiler erlauben, den Sinus als Polynommodell neu zu implementieren, um Präzision gegen Geschwindigkeit einzutauschen, kennt er unsere Zielgenauigkeit immer noch nicht. In C++ können wir nicht sagen, dass „diese Funktion diesen Fehler haben darf“. Wir haben lediglich Compiler-Flags wie „--use-fast-math“ und nur im Rahmen einer Übersetzungseinheit.


  2. Im zweiten Beispiel weiß der Compiler nicht, dass unsere Werte entweder auf 0 oder 1 beschränkt sind, und kann unmöglich die Optimierung vorschlagen, die wir können. Wir hätten es wahrscheinlich durch die Verwendung eines richtigen Bool-Typs andeuten können, aber das wäre ein ganz anderes Problem gewesen.


  3. Und im dritten Beispiel sind die Codeteile so unterschiedlich, dass sie als Synonyme erkannt werden können. Wir haben den Code zu detailliert beschrieben. Wenn es nur std::sort wäre, hätte dies dem Compiler bereits mehr Freiheit bei der Auswahl des Algorithmus gegeben. Es hätte jedoch weder index-sort noch swap sort gewählt, da beide bei großen Arrays ineffizient sind und std::sort mit einem generischen iterierbaren Container funktioniert.


Und so kommen wir zu Spiral . Es ist ein Gemeinschaftsprojekt der Carnegie Mellon University und der Eidgenössischen Technischen Hochschule Zürich. TL&DR: Den Signalverarbeitungsexperten war es langweilig, ihre Lieblingsalgorithmen für jede neue Hardware von Hand neu zu schreiben, und sie haben ein Programm geschrieben, das ihnen diese Arbeit abnimmt. Das Programm verwendet eine allgemeine Beschreibung eines Algorithmus und eine detaillierte Beschreibung der Hardwarearchitektur und optimiert den Code, bis die effizienteste Algorithmusimplementierung für die angegebene Hardware erreicht ist.


Spiral ist ein wichtiger Unterschied zwischen Fortran und Ähnlichem und löst tatsächlich ein Optimierungsproblem im mathematischen Sinne. Es definiert die Laufzeit als Zielfunktion und sucht ihr globales Optimum im durch die Hardwarearchitektur begrenzten Faktorraum der Implementierungsvarianten. Das ist etwas, was Compiler eigentlich nie tun.


Ein Compiler sucht nicht nach dem wahren Optimum. Es optimiert den Code anhand der Heuristik, die ihm von den Programmierern beigebracht wurde. Im Wesentlichen funktioniert ein Compiler nicht als Maschine, die nach der optimalen Lösung sucht, sondern eher als Assembler-Programmierer. Ein guter Compiler funktioniert wie ein guter Assembler-Programmierer, aber das ist alles.




Spiral ist ein Forschungsprojekt. Der Umfang und das Budget sind begrenzt. Aber die Ergebnisse, die es zeigt, sind bereits beeindruckend. Bei der schnellen Fourier-Transformation übertrifft ihre Lösung sowohl MKL- als auch FFTW-Implementierungen deutlich. Ihr Code ist ~2x schneller. Sogar auf Intel.


Um das Ausmaß der Leistung hervorzuheben: MKL ist die Math Kernel Library von Intel selbst, also von den Leuten, die wissen, wie sie ihre Hardware am besten nutzen. Und WWTF, auch bekannt als „Schnellste Fourier-Transformation im Westen“, ist eine hochspezialisierte Bibliothek von den Leuten, die den Algorithmus am besten kennen. Sie sind beide Meister in dem, was sie tun, und die Tatsache, dass Spiral sie beide doppelt schlägt, ist erstaunlich.


Wenn die Optimierungstechnologie, die Spiral verwendet, fertiggestellt und kommerzialisiert wird, werden nicht nur C++, sondern auch Rust, Julia und sogar Fortran einer Konkurrenz ausgesetzt sein, der sie noch nie zuvor ausgesetzt waren. Warum sollte jemand in C++ schreiben, wenn das Schreiben in einer höheren Algorithmusbeschreibungssprache Ihren Code doppelt so schnell macht?


C++-Killer Nummer 2. Numba

Die beste Programmiersprache ist die, die Sie bereits gut kennen. Seit mehreren Jahrzehnten ist C die bekannteste Sprache für die meisten Programmierer. Sie führt auch den TIOBE-Index an, während andere C-likes die Top 10 dicht bevölkern. Doch erst vor zwei Jahren geschah etwas Unglaubliches. Das C gab etwas anderem den ersten Platz.


Das „etwas anderes“ schien Python zu sein. Eine Sprache, die in den 90er Jahren niemand ernst nahm, weil es eine weitere Skriptsprache war, die wir bereits in Hülle und Fülle hatten.



Jemand wird sagen: „Pah, Python ist langsam“ und wird wie ein Idiot dastehen, weil das terminologischer Unsinn ist. Genau wie ein Akkordeon oder eine Bratpfanne kann eine Sprache einfach nicht schnell oder langsam sein. So wie die Geschwindigkeit eines Akkordeons davon abhängt, wer spielt, hängt die „Geschwindigkeit“ einer Sprache davon ab, wie schnell ihr Compiler ist.


„Aber Python ist keine kompilierte Sprache“, könnte jemand fortfahren und erneut eine Fehlentscheidung treffen. Es gibt viele Python-Compiler und der vielversprechendste davon ist wiederum ein Python-Skript. Lassen Sie mich erklären.


Ich hatte einmal ein Projekt. Eine 3D-Drucksimulation, die ursprünglich in Python geschrieben und dann „aus Gründen der Leistung“ in C++ umgeschrieben und dann auf die GPU portiert wurde, und das alles, bevor ich dazu kam. Anschließend habe ich Monate damit verbracht, den Build auf Linux zu portieren und den GPU-Code dafür zu optimieren Tesla M60, da es zu diesem Zeitpunkt das günstigste in AWS war, und Validierung aller Änderungen im C++/CU-Code, damit sie mit dem Originalcode in Python übereinstimmen. Also habe ich alles getan, außer den Dingen, auf die ich mich normalerweise spezialisiert habe, nämlich der Entwicklung geometrischer Algorithmen.


Und als ich endlich alles funktionierte, rief mich ein Student aus Bremen an und fragte: „Du bist also gut in heterogenen Sachen, kannst du mir helfen, einen Algorithmus auf der GPU auszuführen?“ Natürlich! Ich erzählte ihm von CUDA, CMake, Linux-Build, -Tests und -Optimierung; verbrachte vielleicht eine Stunde damit, zu reden. Er hörte sich das alles sehr höflich an, sagte aber am Ende: „Das ist alles sehr interessant, aber ich habe eine ganz konkrete Frage.“ Ich habe also eine Funktion, ich habe @cuda.jit vor ihrer Definition geschrieben, und Python sagt etwas über Arrays und kompiliert den Kernel nicht. Wissen Sie, was hier das Problem sein könnte?“


Ich wusste es nicht. Er hat es innerhalb eines Tages selbst herausgefunden. Anscheinend funktioniert Numba nicht mit nativen Python-Listen, sondern akzeptiert nur Daten in NumPy-Arrays. Also hat er es herausgefunden und seinen Algorithmus auf der GPU ausgeführt. In Python. Er hatte keines der Probleme, mit denen ich Monate verbracht habe. Möchten Sie es unter Linux? Kein Problem, führen Sie es einfach unter Linux aus. Möchten Sie, dass es mit dem Python-Code konsistent ist? Kein Problem, es ist Python-Code. Möchten Sie für die Zielplattform optimieren? Wieder kein Problem. Numba optimiert den Code für die Plattform, auf der Sie den Code ausführen, da er nicht im Voraus kompiliert wird, sondern bei Bedarf kompiliert wird, wenn er bereits bereitgestellt wird.


Ist das nicht großartig? Nun ja, nein. Für mich jedenfalls nicht. Ich habe Monate damit verbracht, mit C++ Probleme zu lösen, die in Numba nie vorkommen, und ein Teilzeitkraft aus Bremen hat das Gleiche in ein paar Tagen geschafft. Es hätte ein paar Stunden dauern können, wenn es nicht seine erste Erfahrung mit Numba gewesen wäre. Was ist also diese Numba? Was für eine Zauberei ist das?


Keine Zauberei. Python-Dekoratoren verwandeln jeden Codeabschnitt für Sie in seinen abstrakten Syntaxbaum, sodass Sie damit machen können, was Sie wollen. Numba ist eine Python-Bibliothek, die abstrakte Syntaxbäume mit jedem vorhandenen Backend und für jede unterstützte Plattform kompilieren möchte. Wenn Sie Ihren Python-Code so kompilieren möchten, dass er auf CPU-Kernen massiv parallel läuft, sagen Sie Numba einfach, dass er ihn so kompilieren soll. Wenn Sie etwas auf der GPU ausführen möchten, sollten Sie wiederum nur fragen .


 @cuda.jit def matmul(A, B, C): """Perform square matrix multiplication of C = A * B.""" i, j = cuda.grid(2) if i < C.shape[0] and j < C.shape[1]: tmp = 0. for k in range(A.shape[1]): tmp += A[i, k] * B[k, j] C[i, j] = tmp


Numba ist einer der Python-Compiler, der C++ überflüssig macht. Theoretisch ist es jedoch nicht besser als C++, da es dieselben Backends verwendet. Es verwendet CUDA für die GPU-Programmierung und LLVM für die CPU. Da nicht für jede neue Architektur ein vorheriger Neuaufbau erforderlich ist, passen sich Numba-Lösungen in der Praxis besser an jede neue Hardware und die verfügbaren Optimierungen an.


Natürlich wäre es besser, einen klaren Leistungsvorteil zu haben, wie es bei Spiral der Fall ist. Aber Spiral ist eher ein Forschungsprojekt, es könnte C++ töten, aber nur irgendwann und nur, wenn es Glück hat. Numba mit Python erwürgt C++ jetzt in Echtzeit. Denn wenn Sie in Python schreiben können und die Leistung von C++ haben, warum sollten Sie dann in C++ schreiben wollen?


C++-Killer Nummer 3. ForwardCom

Lass uns noch ein Spiel spielen. Ich gebe Ihnen drei Codeteile und Sie werden raten, welcher davon oder vielleicht auch mehrere in Assembler geschrieben sind. Hier sind sie:


 invoke RegisterClassEx, addr wc ; register our window class invoke CreateWindowEx,NULL, ADDR ClassName, ADDR AppName,\ WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT, CW_USEDEFAULT,\ CW_USEDEFAULT, CW_USEDEFAULT,\ NULL, NULL, hInst, NULL mov hwnd,eax invoke ShowWindow, hwnd,CmdShow ; display our window on desktop invoke UpdateWindow, hwnd ; refresh the client area .while TRUE ; Enter message loop invoke GetMessage, ADDR msg,NULL,0,0 .break .if (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .endw


 (module (func $add (param $lhs i32) (param $rhs i32) (result i32) get_local $lhs get_local $rhs i32.add) (export "add" (func $add)))


 v0 = my_vector // we want the horizontal sum of this int64 r0 = get_len ( v0 ) int64 r0 = round_u2 ( r0 ) float v0 = set_len ( r0 , v0 ) while ( uint64 r0 > 4) { uint64 r0 >>= 1 float v1 = shift_reduce ( r0 , v0 ) float v0 = v1 + v0 }


Welches oder welche sind also in der Versammlung? Wenn Sie das für alle drei halten, herzlichen Glückwunsch! Deine Intuition ist schon viel besser geworden!


Der erste ist in MASM32. Es handelt sich um einen Makroassembler mit „Wenn“- und „Während“-Zeichen, in denen die Leute native Windows-Anwendungen schreiben. Das ist richtig, nicht „zum Schreiben verwendet“, sondern „zum Schreiben“ bis heute. Microsoft schützt eifrig die Abwärtskompatibilität von Windows mit der Win32-API, sodass alle jemals geschriebenen MASM32-Programme auch auf modernen PCs gut funktionieren.


Ironischerweise wurde C erfunden, um die UNIX-Übersetzung von PDP-7 nach PDP-11 zu vereinfachen. Es wurde als tragbarer Assembler konzipiert, der in der Lage ist, die kambrische Explosion der Hardwarearchitekturen der 70er Jahre zu überstehen. Aber im 21. Jahrhundert entwickelt sich die Hardware-Architektur so schleppend, dass die Programme, die ich vor 20 Jahren in MASM32 geschrieben habe, heute perfekt zusammengebaut werden und laufen, aber ich habe kein Vertrauen, dass eine C++-Anwendung, die ich letztes Jahr mit CMake 3.21 erstellt habe, heute mit CMake erstellt werden kann 3.25.


Der zweite Codeteil ist Web Assembly. Es ist nicht einmal ein Makro-Assembler, es gibt keine „Wenn“- und „Während“-Zeichen, es ist eher ein für Menschen lesbarer Maschinencode für Ihren Browser. Oder ein anderer Browser. Konzeptionell jeder Browser.


Web Assembly-Code hängt überhaupt nicht von Ihrer Hardwarearchitektur ab. Die Maschine, der sie dient, ist abstrakt, virtuell, universell – nennen Sie sie, wie Sie wollen. Wenn Sie diesen Text lesen können, haben Sie bereits einen auf Ihrem physischen Computer.


Aber der interessanteste Teil des Codes ist der dritte. Es ist ForwardCom – ein Assembler, den Agner Fog, ein renommierter Autor von C++- und Assembly-Optimierungshandbüchern, vorschlägt. Genau wie bei Web Assembly umfasst der Vorschlag weniger einen Assembler als vielmehr den universellen Befehlssatz, der nicht nur Abwärts-, sondern auch Vorwärtskompatibilität ermöglichen soll. Daher der Name. Der vollständige Name von ForwardCom lautet „ eine offene vorwärtskompatible Befehlssatzarchitektur “. Mit anderen Worten: Es handelt sich weniger um einen Versammlungsvorschlag als vielmehr um einen Friedensvertragsvorschlag.


Wir wissen, dass alle gängigsten Architekturfamilien: x64, ARM und RISC-V unterschiedliche Befehlssätze haben. Aber niemand kennt einen guten Grund, es so beizubehalten. Alle modernen Prozessoren, mit Ausnahme vielleicht der einfachsten, führen nicht den Code aus, mit dem Sie sie füttern, sondern den Mikrocode, in den sie Ihre Eingaben übersetzen. Es ist also nicht nur M1, der über eine Abwärtskompatibilitätsschicht für Intel verfügt, sondern jeder Prozessor verfügt im Wesentlichen über eine Abwärtskompatibilitätsschicht für alle seine eigenen früheren Versionen.


Was hindert Architekturdesigner also daran, sich auf eine ähnliche Ebene zu einigen, außer aus Gründen der Vorwärtskompatibilität? Abgesehen von den widersprüchlichen Ambitionen der Unternehmen, die im direkten Wettbewerb stehen, nichts. Aber wenn sich die Prozessorhersteller irgendwann damit zufrieden geben, einen gemeinsamen Befehlssatz zu haben, anstatt für jeden anderen Konkurrenten eine neue Kompatibilitätsebene zu implementieren, wird ForwardCom die Assemblerprogrammierung wieder in den Mainstream bringen. Diese Vorwärtskompatibilitätsschicht würde die schlimmste Neurose jedes Assembler-Programmierers da draußen heilen: „Was wäre, wenn ich den einmaligen Code für diese bestimmte Architektur schreibe und diese bestimmte Architektur in einem Jahr veraltet wäre?“


Mit einer Vorwärtskompatibilitätsschicht wird es niemals veraltet sein. Das ist der Punkt.


Die Assembler-Programmierung wird auch durch den Mythos gebremst, dass das Schreiben in Assembler schwierig und daher unpraktisch sei. Fogs Vorschlag befasst sich auch mit diesem Problem. Wenn die Leute denken, dass das Schreiben in Assembler schwierig ist und das Schreiben in C nicht, dann lassen wir den Assembler einfach wie C aussehen. Kein Problem. Es gibt keinen guten Grund dafür, dass eine moderne Assemblersprache genauso aussieht wie ihr Großvater in den 50er Jahren.


Sie haben gerade selbst drei Montagebeispiele gesehen. Keine davon sieht aus wie eine „traditionelle“ Versammlung und sollte es auch nicht sein.


ForwardCom ist also die Assembly, in der Sie optimalen Code schreiben können, der niemals veraltet ist und für die Sie keine „traditionelle“ Assembly lernen müssen. Aus praktischer Sicht ist es das C der Zukunft. Nicht C++.

Wann wird С++ endlich sterben?

Wir leben in einer postmodernen Welt. Nichts stirbt mehr außer Menschen. So wie Latein nie wirklich gestorben ist, genau wie COBOL, Algol 68 und Ada, ist C++ zur ewigen Halbexistenz zwischen Leben und Tod verdammt. C++ wird nie wirklich sterben, es wird nur durch neuere, leistungsfähigere Technologien aus dem Mainstream verdrängt.


Nun ja, nicht „wird gedrängt“, sondern „gedrängt werden“. Ich bin zu meinem jetzigen Job als C++-Programmierer gekommen und heute beginnt mein Arbeitstag mit Python. Ich schreibe die Gleichungen, SymPy löst sie für mich und übersetzt die Lösung dann in C++. Ich füge diesen Code dann in die C++-Bibliothek ein und mache mir nicht einmal die Mühe, ihn ein wenig zu formatieren, da clang-tidy das sowieso für mich erledigt. Ein statischer Analysator überprüft, ob ich die Namespaces nicht durcheinander gebracht habe, und ein dynamischer Analysator prüft, ob Speicherlecks vorliegen. CI/CD kümmert sich um die plattformübergreifende Kompilierung. Ein Profiler hilft mir zu verstehen, wie mein Code tatsächlich funktioniert, und ein Disassembler – warum.


Wenn ich C++ gegen „nicht C++“ eintausche, bleiben 80 % meiner Arbeit genau gleich. C++ ist für die meisten meiner Tätigkeiten einfach irrelevant. Könnte es bedeuten, dass C++ für mich bereits zu 80 % tot ist?



Durch stabile Diffusion entwickeltes Leitbild.