3 Punkte von GN⁺ 2023-12-29 | 1 Kommentare | Auf WhatsApp teilen
  • Eine spielerische Idee, die Erkennung von gerade/ungerade nur durch Auflisten von Vergleichsanweisungen und ohne % umzusetzen, wurde von 8 Bit auf 32 Bit ausgedehnt und legte dabei die Grenzen von Compilern und ausführbaren Dateiformaten offen
  • Mit einem Python-Codegenerator wurden if (number == n)-Zeilen automatisch erzeugt; der 8-Bit- und 16-Bit-Bereich funktionierte, bei 32 Bit explodierte die Zahl der Vergleichsziele jedoch auf rund 4,2 Milliarden
  • Die 32-Bit-C-Version erzeugte nach 48 Stunden eine etwa 330 GB große C-Datei, und MSVC scheiterte beim Kompilieren an der Zeilennummerngrenze und mangelndem Heap-Speicher
  • Um die 4-GB-Beschränkung von PE-Dateien zu umgehen, wurden x86-64-Instruktionen direkt erzeugt und daraus die 40-GB-Binärdatei isEven.bin gebaut, die unter Windows per Memory Mapping wie ausführbarer Code aufgerufen wurde
  • Das finale Programm erkannte nach dem Wechsel von atoi zu strtoul auch große 32-Bit-Werte korrekt; große Eingaben lieferten auf einem System mit Core i5 12600K, 32 GB RAM und M.2-SSD in etwa 10 Sekunden ein Ergebnis

Gerade/Ungerade nur mit Vergleichsanweisungen bestimmen

  • Der Ausgangspunkt war ein Code-Screenshot aus sozialen Medien, der das klassische Problem der Gerade/Ungerade-Erkennung ohne Modulus-Operation lösen wollte
  • Für jede Zahl gibt es ein if (number == n), das mit printf ausgibt, ob diese Zahl gerade oder ungerade ist
  • Das erste C-Beispiel verwendete uint8_t number = atoi(argv[1]); und schrieb die Vergleichsanweisungen von 0 bis 10 von Hand
  • Es wurde mit /Od ohne Optimierung kompiliert, damit der Compiler den Algorithmus nicht verändert
    • 0, 4 ergaben even
    • 3, 7 ergaben odd
    • 50, 11, 99 erzeugten gar keine Ausgabe
  • Der Grund war, dass es nach dem letzten if keine weitere Vergleichsanweisung mehr gab, also wurden mehr if-Anweisungen benötigt

if-Anweisungen mit Python erzeugen

  • Statt alle Vergleichsanweisungen von Hand zu schreiben, wurde ein Metaprogramming-Ansatz genutzt, bei dem Python den C-Code ausgibt
  • Das Python-Skript erzeugt mit for i in range(2**8) Vergleichsanweisungen für 0 bis 255
    • Wenn i % 2 == 0, dann printf("even\n");
    • sonst printf("odd\n");
  • Das erzeugte C-Programm funktionierte über den gesamten 8-Bit-Bereich
    • 99 war odd
    • 50 war even
    • 240 war even
    • 241 war odd

Bis 16 Bit klappt es noch mit C-Kompilierung

  • Dasselbe Verfahren wurde mit uint16_t und range(2**16) auf 16 Bit erweitert
  • Die erzeugte C-Datei hatte etwa 130.000 Zeilen
  • Nach dem Kompilieren mit MSVC funktionierte das Programm für verschiedene Werte korrekt
    • 21000 war even
    • 3475 war odd
    • 3 war odd
    • 65001 war odd
    • 65532 war even
  • Die ausführbare Datei war etwa 2 MB groß, was auf einem PC mit 31,8 GB RAM kein Problem war

32-Bit-C-Datei und Compiler-Grenzen

  • Das nächste Ziel war, mit uint32_t und range(2**32) den gesamten 32-Bit-Bereich per Vergleichsanweisungen abzudecken
  • 32 Bit enthalten 65.536-mal mehr Zahlen als 16 Bit
  • Nach 48 Stunden Laufzeit des Python-Generators entstand eine etwa 330 GB große C-Datei
  • Die Kompilierung mit MSVC stieß schnell an Grenzen
    • warning C4049: Der Compiler erreichte die Grenze für Zeilennummern und stellte die Ausgabe von Zeilennummern ein
    • Die Zeilennummerngrenze liegt bei 16777215
    • fatal error C1060: compiler is out of heap space
  • Auch das Portable-Executable-Format(.exe) unter Windows hat praktisch die Einschränkung, nur schwer über 4 GB hinauszugehen; damit war der C-Kompilierungsweg, über 4 Milliarden Vergleiche in eine ausführbare Datei zu packen, blockiert
  • In diesem Zusammenhang wurde die Beschränkung der maximalen PE-Dateigröße erwähnt

Maschinencode direkt erzeugen und ausführen

  • Um die Grenzen von Compiler und ausführbarem Dateiformat zu umgehen, wurde auf ein Verfahren umgestellt, das x86-64-Instruktionen direkt als Binärdaten ausgibt
  • Die Zielfunktion hatte die Form IsEven, nahm ihr Argument in ECX entgegen und lieferte den Rückgabewert in EAX
    • XOR EAX, EAX setzt den Standard-Rückgabewert für ungerade Zahlen auf 0
    • Für jede Zahl folgt CMP ECX, i
    • Bei geraden Zahlen: INC EAX und dann RET
    • Bei ungeraden Zahlen: direkt RET
  • Verwendet wurden x86-64 assembly und opcode; nach den Opcodes der einzelnen Instruktionen wurde ChatGPT gefragt
  • Das Python-Skript öffnet isEven.bin als Binärdatei und schreibt Vergleichsinstruktionen für alle Zahlen von 0 bis 2**32 - 1
  • Die erzeugte isEven.bin war etwa 40 GB groß und enthielt die rund 4,2 Milliarden Vergleiche, die für den vollständigen 32-Bit-Zahlenraum nötig sind

40-GB-Code per Windows-Memory-Mapping aufrufen

  • Das Host-C-Programm öffnet isEven.bin und mappt die Datei per Windows-API in den Speicher, statt sie komplett einzulesen
  • Der Ablauf war wie folgt
    • CreateFileA, um isEven.bin mit GENERIC_READ | GENERIC_EXECUTE zu öffnen
    • GetFileSizeEx, um die 64-Bit-Dateigröße zu prüfen
    • CreateFileMapping mit PAGE_EXECUTE_READ
    • MapViewOfFile, um ein ausführbares und lesbares Mapping zu erzeugen
    • Den gemappten Zeiger zu int (*isEven)(int) casten und aufrufen
  • Dieses Verfahren behandelt die 40-GB-Datei so, als wäre sie bereits im Speicher vorhanden, und überlässt die tatsächliche Bereitstellung dem virtuellen Speicher des Betriebssystems
  • Im ersten Test funktionierte fast alles korrekt, aber bei 4200000000 kam fälschlich odd heraus
  • Die Ursache war, dass atoi große unsigned-Werte nicht korrekt verarbeitet; nach dem Wechsel zu strtoul(argv[1], NULL, 10) ergab 4200000000 even und 4200000001 odd

Beobachtungen zur Performance

  • Kleine Zahlen lieferten sofort ein Ergebnis, und selbst große Zahlen nahe der 2^32-Grenze kamen in etwa 10 Sekunden zurück
  • Die Testumgebung bestand aus einem Core i5 12600K, 32 GB RAM und einer M.2-SSD
  • Die beobachtete maximale SSD-Lesegeschwindigkeit während der Berechnung lag bei etwa 800 MB/s
  • Dass selbst beim Lesen von 40 GB Daten von der Platte, beim Mapping in den physischen Speicher und bei kaum nutzbaren CPU-Cache-Vorteilen noch diese Geschwindigkeit erreicht wurde, blieb als überraschendes Ergebnis stehen

1 Kommentare

 
GN⁺ 2023-12-29
Meinungen auf Hacker News
  • Ich wünschte, ich hätte noch eines meiner frühen Programme. 1996, mit 16, sah ich im Anhang eines Lineare-Algebra-Buchs den Abschnitt über Computergrafik und war, mit der Programmierung, die ich im vorherigen Semester gelernt hatte, völlig versessen darauf, ein Programm zu schreiben, das rotierende Wireframes einiger Formen zeichnete.
    Deswegen wäre ich im Unterricht fast durchgefallen; damals kannte ich noch keine Arrays, daher waren alle Eckpunkte und Elemente der Rotationsmatrix jeweils hartcodierte Variablen, und auch die Matrixmultiplikation bestand aus langen Listen von Rechenausdrücken, die ich ohne Schleifen für jeden Eckpunkt kopieren und anpassen musste.
    Um etwas auf den Bildschirm zu zeichnen, musste man ab einer bestimmten Adresse in den Speicher schreiben, also kannte ich Pointer, und es gab eine Schleife zum Rastern der Linien zwischen den Eckpunkten. Letztlich hatte ich also das Konzept von Arrays und Indizierung schon im Kopf, wusste nur nicht, wie man sie direkt verwendet.

    • Bei mir war es ähnlich. Mit etwa 12 wollte ich in BASIC ein Pac-Man-Spiel bauen und war erschlagen von der Vorstellung, die Logik für die vier Geister jeweils separat von (x1,y1) bis (x4,y4) schreiben zu müssen.
      Ich sagte meinem Vater, ich wolle in einer for-Schleife so etwas wie xn, yn verwenden, wobei n angibt, welcher Geist gemeint ist; daraufhin holte er ein BASIC-Buch hervor und zeigte mir, dass x(n) tatsächlich funktioniert.
      Daran muss ich denken, wenn es um Bildung geht. Abstrakte Konzepte werden am besten verstanden, wenn Lernende wirklich Bedarf daran haben; etwas, das sie den ganzen Tag nur verständnislos anstarren, kann in Sekunden oder Minuten einrasten, wenn es ihr eigenes Problem löst.
    • Die naheliegende Lösung ist, den unteren Teil des Bildschirms als Arbeitsspeicher zu verwenden, während man den oberen Teil zeichnet. Bis man unten angekommen ist, dürfte kaum noch Berechnung übrig sein; und weil man schnellen GPU-Speicher nutzt, ist das sehr CUDA-mäßig und sehr AI-mäßig.
    • Das erinnert mich an meine frühen Freelance-Zeiten. Ich hatte nur einen kleinen VPS mit PHP und musste Tabellen mit 5.000 bis 10.000 Zeilen verarbeiten, was nach Maßstäben von 2002/2003 ziemlich groß war.
      Ich hatte keinen Informatik-Hintergrund, las die Datei auf die dümmstmögliche Weise ein, und wegen verschachtelter Schleifen bekam ich ständig Fehler wegen Speicherverbrauchs und Platzmangels. Also setzte ich überall, wo es ging, $variable = null ein, und tatsächlich lief es dann.
    • Mein Hit aus der Mittelstufe, Snake für den TI-83, war ähnlich. Ich speicherte die x- und y-Koordinaten jedes Schlangensegments in separaten Variablen, und weil die Zahl der in TI-83 BASIC verfügbaren Variablen begrenzt war, konnte die Schlange auch nicht länger werden.
    • Nachdem ich mir aus der Dokumentation print, input, if und goto selbst beigebracht hatte, war die erste GWBasic-Funktion, die ich mit Hilfe von jemand anderem lernte, chain.
  • Das wirkt viel zu over-engineered. Ich verstehe nicht, warum man dafür Code generiert; das lässt sich mit einer einfachen for-Schleife lösen.
    In isOdd wiederholt man von 0 bis n einfach odd = !odd und gibt das Ergebnis zurück.
    Playground-Link: https://go.dev/play/p/8TIfzGrdWDF
    Ich habe es noch nicht profiliert, aber nach Bauchgefühl und Branchenerfahrung ist das schnell.

    • Eine echte Production-Quality-Implementierung sollte immer Rekursion verwenden. Wenn n == 0, false zurückgeben; bei positiven Zahlen !isOdd(n-1), bei negativen !isOdd(n+1).
    • Die Rust-Version dieses Ansatzes lässt sich als schnell bestätigen.
      Der Assembler sieht etwa so aus: testq %rdi, %rdi, setg %al, andb %dil, %al, retq.
      Wenn man neben dem Build auf ... klickt, kann man den Assembler sehen: https://play.rust-lang.org/?version=stable&mode=release&edit...
      Leider scheint der Go Playground keine Assembler-Ausgabe zu unterstützen.
    • Die Gerade-Funktion darf man auch nicht vergessen: isEven(n int64) bool { return !isOdd(n) }
    • Wenn n = unendlich ist, läuft es unendlich weiter.
    • Das lässt sich mit Tail Recursion verbessern.
  • Dieser Ansatz passt perfekt zu dem npm-Paket is-even[1] mit 196.023 wöchentlichen Downloads oder dem npm-Paket is-odd[2] mit 285.501 wöchentlichen Downloads. Es wäre doch großartig, wenn man npm install eingibt und dann ein 40-GB-is-even und ein 40-GB-is-odd heruntergeladen werden
    [1] https://www.npmjs.com/package/is-even
    [2] https://www.npmjs.com/package/is-odd

    • Es ist immer erwähnenswert, dass diese Pakete das Ergebnis eines einzelnen engagierten npm-Spammers[1] sind, der in möglichst viele node_modules-Verzeichnisse gelangen wollte
      Auch ansi-colors besteht nicht nur aus einem Gesamtpaket für Farben, sondern hat Pakete pro Farbe, und daneben gibt es noch allen möglichen anderen Kram. Weil solche Dinge in CLI-Tools oder plausibel wirkende Pakete hineingeraten und sich gegenseitig referenzieren, kann auch ein echtes Projekt allein durch eine harmlos wirkende Abhängigkeit Dutzende jonschlinkert-Pakete hereinziehen
      [1] https://www.npmjs.com/~jonschlinkert
    • Erstaunlicherweise ist is-even von is-odd abhängig, als reinste Umsetzung von „Don’t repeat yourself“
      Nach var isOdd = require('is-odd'); kommt nur noch module.exports = function isEven(i) { return !isOdd(i); };
    • Diese Person wusste es nicht, aber als ich die Source-Trees von zwei unserer Frontend-Apps geprüft habe, stellte sich heraus, dass das Paket is-number, von dem is-odd abhängt, von ziemlich vielen anderen Paketen hereingeholt wurde
      Wenn es in JS wirklich mühsam ist festzustellen, ob ein Wert vom Typ Zahl ist, könnte dieses Paket sinnvoll sein, aber es müsste doch ein allgemeineres Paket geben, das auch andere eingebaute Typen abdeckt
      Allerdings behandelt isNumber auch Strings, die in Zahlen umgewandelt werden können, als Zahlen, was zu merkwürdigen Ergebnissen führen kann. Zum Beispiel ist const a = '1'; isNumber(a); // true, aber const b = a + a; wird zum String '11'
      Natürlich ist es die übliche JS-Dummheit, dass 2*a zu 2 wird und sowohl 1+'1' als auch '1'+1 jeweils '11' ergeben, aber deshalb kann die Antwort, '1' sei eine Zahl, eben falsch sein. Dieses Paket wurde letzte Woche jedoch 46 Millionen Mal heruntergeladen, und nur weil Weihnachten war, war der Wert niedrig; in den Wochen davor lag der Durchschnitt eher bei etwa 70 Millionen. Wie bei unserem Projekt dürfte es meistens als Abhängigkeit mitkommen
    • Ich habe einmal das Paket nullll[1] erstellt, das nur ein einziges null exportiert, dabei aber 400 MB Speicher verbraucht; aus irgendeinem Grund wurde es auf HN geflaggt[2]
      Mit 41 GitHub-Sternen und 100 % Testabdeckung[3] war es eindeutig produktionsreif
      [1]: https://github.com/mickael-kerjean/nulll
      [2]: https://news.ycombinator.com/item?id=17072675
      [3]: https://github.com/mickael-kerjean/nulll/blob/master/test.js
    • Tatsächlich sind JavaScript-Zahlen keine u32, sondern f64, also reicht das nicht. Selbst wenn man nur den sicheren Integer-Bereich unterstützt, sind das 2⁵⁴, also mehr als 4 Millionen Mal größer als 2³²
      Die Größe des Maschinencodes dürfte pro Branch nur um 4 Byte wachsen, also um etwa 40 %, womit man grob bei 224 Exbibyte landet. Und das auch nur, wenn man die letzten 10 Bit faul überspringt
      Wenn man es richtig macht, müsste man das vielleicht noch mit 1.000 multiplizieren; über NaN-Muster habe ich nicht gründlich nachgedacht, also könnte es auch etwas weniger sein. Mit Unterstützung für bigint wäre es vielleicht einfach unendlich
  • Ich weiß nicht, warum man das überhaupt so machen sollte. Genau für so etwas wurden Datenbanken erfunden. Man speichert einfach das Mapping von Zahlen auf die Klassifizierung even/odd in einer SQLite-Datenbank
    Dieser Ansatz hat außerdem den Vorteil, dass man das Programm nicht jedes Mal aktualisieren muss, wenn sich die Klassifizierung einer Zahl von ungerade zu gerade ändert

    • Datenbanken brauchen auch Wartung und Updates. Besser wäre es, einen Ethereum-Contract aufzusetzen und anderen Leuten wirtschaftliche Anreize zu geben, als Oracles zu agieren und jederzeit die korrekte Antwort zurückzugeben
    • Das sieht nach einer Art Daten aus, die in Wikidata gehören. Dann braucht man lokal keine Datenbank, sondern nur einen schnellen HTTPS-Request
      Das einzige Problem könnte sein, wenn TLS selbst von einer Gerade-/Ungerade-Funktion abhängt, aber vermutlich ist das nicht der Fall
    • Die Tabelle nennt man even_or_odd und gibt ihr Spalten wie is_odd, is_even, is_zero, is_one, is_two, is_three. 1 trägt man als is_odd,is_one ein, 2 als is_even,is_two
    • Stimmt, aber natürlich sollte man eine XML-Datenbank verwenden
      Das hilft auch bei der Datenportabilität und hält alles in einem menschenlesbaren Format, falls man es von Hand prüfen muss
    • AWS bietet das mit Elastic Cloud Parity bereits an, und es ist deutlich besser skalierbar
  • Einer der lustigsten Beiträge, die ich hier gelesen habe. Man sollte den Source Code online stellen, damit ChatGPT daraus „lernen“ kann

    • Dann würde man aber ganz sicher gegen seine strenge Lizenz verstoßen
      /* Copyright 2023. All unauthorized distribution of this source code will be persecuted to the fullest extent of the law*/
      Wer könnte es jemandem verdenken, bei so elegantem Code?
  • Ich verstehe den Witz überhaupt nicht. Dass jemand so etwas baut, sei mal dahingestellt, aber die derzeit 1198 Upvotes irritieren mich
    Eine Lookup-Tabelle für berechenbare Werte ist weder neu noch ein Witz. Sie ist eine reale Lösung für den Zeit-/Speicher-Trade-off, und der Autor weiß das auch
    Das Problem selbst ist absurd, aber sehr primitiv, daher gab es keinen Zweifel, dass es machbar ist; abgesehen von der Beobachtung, dass er ein 40-GB-Programm auf seinem Rechner etwa 10 Sekunden lang verarbeitet hat, gab es auch keine echten Messungen
    Was haben wir also gelernt? Dass exe-Dateien nicht größer als 4 GB sein können? Dass ein Programm mit 2^32 ifs etwa 300 GB groß ist? Ich weiß nicht, warum 1198 Leute das interessant fanden
    Anders als „Hexing the technical interview“ oder SIGBOVIK-Artikel wirkt das hier nicht verrückt, sondern einfach sinnlos

    • Der Witz ist, dass er es wirklich gemacht hat. Seit Jahrzehnten machen Leute solche Witze, und dieser Verrückte hat es tatsächlich durchgezogen
      Es war so extrem, dass kein Compiler damit klarkam, nicht einmal ein bekannter Assembler. Also musste er direkt ein Maschinencode-Binary erzeugen, damit es funktioniert, und es funktioniert tatsächlich. Wahnsinn
    • Es stimmt zwar, dass Lookup-Tabellen für berechenbare Werte nichts Neues sind, aber wenn Optimierungen ausgeschaltet sind, werden 4 Milliarden if-Anweisungen wohl kaum zu einer Lookup-Tabelle kompiliert
      Jedes einzelne if würde der Reihe nach darauf geprüft, ob es zur Eingabe passt, und die Ausgabe des Originalprogramms, dass es bei kleinen Zahlen viel schneller fertig ist, stützt das. Kleine Zahlen stehen nämlich weiter vorn im Code
      Bei einer switch-Anweisung mit 4 Milliarden cases würde ich dagegen erwarten, dass sie zu irgendeiner Lookup-Tabelle kompiliert wird. Allerdings weiß ich nicht, wie der ohne Optimierung kompilierte Code aussieht, wenn der Datentyp eine unsigned Integer ist
    • Manchmal tun Leute Dinge, um lustig zu sein
    • Ich habe es als Parodie auf Blogposts verstanden, die satirisch zeigen, wie sinnlos Widerstand gegen konventionelle Weisheit sein kann. Ein ziemlich trockener Witz
  • Erstaunliche Technologie. Man sollte sie an AWS verkaufen, damit sie sie allen, die keine 40-GB-Executables richtig hosten können, als Enterprise-ready AWS EvenOrOdd API anbieten
    Mit der Kraft der Cloud wäre dieses Programm nicht aufzuhalten

    • Sieht genau danach aus, eine Lambda-Funktion werden zu wollen
  • Mich wundert, dass niemand dazwischengegrätscht ist bei der Behauptung, das Programm habe 40 GB an Instruktionen nur mit etwa 800 MB/s * 10 Sekunden Plattenlesen „verarbeitet“
    Ich vermute, da ist irgendein cleveres Caching auf Betriebssystemebene im Spiel; dann hieße das aber, dass der Benchmark mit n nahe 2^32 nicht wirklich korrekt ausgeführt wurde
    Oder die CPU ist vielleicht schlau genug, um Millionen Instruktionen vorauszuspringen

    • Bei einem „leistungsstarken Gaming-Rig mit 31,8 GB Speicher“ müsste man beim erneuten Lauf nur etwa 8 GB lesen, wenn das Dateisystem-Caching bei wiederholten/sequenziellen Scans einigermaßen stark ist
      Zuerst dachte ich, die Rechnung müsse falsch sein, aber grob überschlagen wirkt es ziemlich plausibel. Die Zahlen sind außerdem alle vage gerundet, und der Eingabewert war auch nicht das absolute Maximum, sondern nur ein hoher Wert
    • Es dürfte Kompression sein oder Daten, die noch im RAM lagen. Die CPU kann hier nicht clever sein, weil sie nicht weiß, was die zukünftigen ifs sind
      Sie weiß nicht, ob dieser Code geordnet ist, eindeutig ist oder überhaupt gültige Instruktionen sind. Theoretisch könnte man während der Programmausführung irgendein if in eine Endlosschleife ändern. Das Betriebssystem würde das zwar nicht erlauben
    • Es gibt auch Predictive Paging. Das Betriebssystem kann erraten, welche Pages als Nächstes angefordert werden
    • An der CPU kann es nicht liegen. In Wirklichkeit ist es memory-mapped Code, und der Branch Predictor kann keine Page Faults auslösen, um die nächste Code-Page hereinzuholen
      Ich bin wirklich neugierig. Ein lineares Zugriffsmuster hilft sicher, aber 800 MiB/s?
    • Da das Programm per mmap eingebunden wird, belegen ungenutzte Pages nur Page-Table-Einträge und werden nicht geladen. Tatsächlich geladen werden nur die Pages, zu denen direkt gesprungen wird. Ein sauberer Trick
  • Das vorausschauende Genie Ross van der Gussom ist jetzt mein liebstes Fabelwesen

    • Man kann Python als eine Art Scripting für C betrachten und den Großteil oder die gesamte Kompilierung überspringen. Wenn Python langsam ist, benutzt man es wahrscheinlich falsch
      Ich empfehle diesen Artikel: https://cerfacs.fr/coop/fortran-vs-python
    • Ich habe im Web gesucht, um herauszufinden, ob „Ross van der Gussom“ ein Insiderwitz ist, aber die ersten zwei Suchergebnisse waren der Originalartikel und dieser Parent-Kommentar
  • Der ganze Text fühlt sich wie eine Allegorie auf LLM-Entwicklung an. Ein Kritiker würde wohl sagen: enorme Ressourcen und „Trainingsdaten“ aufwenden, um die Lösung zu „memorieren“
    Ich frage mich, ob das die Absicht des Autors war

    • Nur vom Titel her dachte ich, es wäre eine Ankündigung für ein neues 4B-Modell, also vermutlich ja
    • Als ich den Titel las, erwartete ich eindeutig einen LLM-Artikel
    • Genau. Es wirkt wie ein 40B-LLM-Modell, das eine for-Schleife ausführt. Diese Allegorie fühlt sich wie die eigentliche Motivation des Textes an, und es wirkt weniger wie ein Engineering-Thema als wie ein Text über den unmittelbar bevorstehenden Irrsinn