Zusammenfassung der vorherigen Episoden
- Between 2020 and 2022, Darktable underwent a mass-destruction enterprise, by a handful of guys with more freetime and benevolence than actual skills,
- In 2022, I started noticing an annoying lag between GUI interactions with sliders controls and feedback/update of said sliders. For lack of feedback stating that the value change was recorded, users could change it again, thereby starting additionnal pipeline recomputes and effectively freezing their computer because stupid GUI never said “got you, wait for a bit now”.
- I discovered that pipeline recomputations orders were issued twice per click (once on “button pushed”, once on “button released” events), and once again for each mouse motion, but also that the GUI states were updated seemingly after pipe recompute.
- I fixed that by almost rewriting the custom GUI controls (Bauhaus lib). I thought that preventing reckless recompute orders was gonna solve the lag : it didn’t. Then, I discovered that requesting a new pipeline recompute before the previous ended waited for the previous to end, despite a shutdown mechanism implemented many years ago that should have worked.
- I fixed that by implementing a kill-switch mechanism on pipelines, following comments in the code from the 2010’s and internal utilities that may well have never worked. This did not always work because the kill order came often with a noticeable delay. Once again, the GUI lag was not fixed.
…
Episode 5: die technische Schuld zurückzahlen
Was ich entdeckt habe, sollte wirklich in die Lehrbücher der Informatik im Kapitel darüber, was nicht zu tun ist, wenn man eine halbwegs zuverlässige Anwendung schreiben möchte.
Also, wann immer ein Bildbearbeitungsparameter in einem Modul geändert wurde, wurde eine Anfrage gesendet, um einen neuen Verlaufseintrag in die Datenbank hinzuzufügen (oft mehr als einmal pro Interaktion, wie oben gezeigt). Verlaufseinträge sind nichts weiter als ein Schnappschuss eines Moduls’ interner Parameter (einschließlich Masken). Wenn eine Änderung im Vergleich zum vorherigen Verlaufseintrag festgestellt wurde, wurde ein PIPELINE_STATE
-Flag auf den Wert DIRTY
gesetzt, um anzuzeigen, dass die Pipeline eine Neuberechnung benötigt, und ein gtk_widget_queue_draw()
wurde gesendet, was, wie der Name vermuten lässt, Gtk bittet, die Hauptvorschau der Dunkelkammer und das Navigations-Thumbnail neu zu zeichnen, aber asynchron (verstehe: wann immer es die Zeit findet, nachdem alles zuvor begonnene abgeschlossen ist). Dies wird später noch wichtig sein.
Es hat mich eine sehr lange Zeit gekostet herauszufinden, wie die Pipeline tatsächlich gestartet wurde, weil keiner der an Module und Pipeline angehängten Codes etwas enthielt, das sagte: “berechne das”. Mit anderen Worten: Keiner der Modulcodes enthielt explizite Neuberechnungsanweisungen.
Ich musste den Pipeline-Code von der anderen Seite rückentwickeln, um herauszufinden, wie die Pipeline gestartet werden könnte. Ich suchte nach jeder Option und grepte jede Option, bis ich das unaussprechliche herausfand: Die erste Generation von Darktable-Entwicklern hatte eine Callback-Funktion an das redraw
-Ereignis auf der Hauptvorschau und dem Navigations-Thumbnail der Dunkelkammer angeschlossen, aber an einem völlig unzusammenhängenden Ort im Code. In diesem GUI-Callback wurde der Wert des PIPELINE_STATE
-Flags geprüft und entweder das Backbuffer-Pixmap direkt an das Widget gesendet, wenn das Flag VALID
war, oder eine Neuberechnung der Pipeline angefordert, wenn das Flag DIRTY
war und diese Neuberechnung selbst einen gtk_widget_queue_draw()
nach Abschluss anforderte.
Diese Methode hat ein Verdienst: Es ist faules Programmieren. Dann hat sie eine Menge Nachteile und Probleme:
- es ist nicht entwicklerfreundlich, besonders in einem Softwareprojekt, wo die Suche im Code der einzige Traum von Dokumentation ist. Es dauerte viele Stunden, die Logik durch programmarchäologische Arbeiten zu verstehen. Wenn ein Befehl erteilt wird, möchte ich
command_issued()
an der richtigen Stelle im Code lesen, weil C schon schwierig genug zu verfolgen ist, ohne Rätsel in Debuggen zu mischen. - Da
gtk_widget_queue_draw()
(im schlimmsten Fall zweimal aufgerufen) nur in die Warteschlange gestellt wird und asynchron verarbeitet wird, fügt es jegliches Lag hinzu, das Gtk erleiden könnte (während der Verarbeitung anderer Teile der GUI oder vorheriger Frames), bevor eine Pipeline-Neuberechnung überhaupt gestartet ist, was unnötig ist, da die Pipeline in ihrem eigenen Thread parallel läuft, - das große MIDI-Triple-Decker, das auf Zeige-, Tastatur- und MIDI-Ereignisse hörte, um Verknüpfungen zu verteilen, scheint die globale GUI durch Schleifen über alle bekannten Verknüpfungen überlastet zu haben, was Gtk dazu brachte, so stark zu laggen, dass es auffällig wurde,
- es verhindert, dass jeder Kill-Switch-Mechanismus nützlich ist, sowohl wegen der Verzögerung als auch weil die Lesezugriffe auf Flags mit Thread-Sperren (und Race-Bedingungen) durchzogen waren. Darüber hinaus würde das Warten auf die Aufnahme der Pipeline-Thread-Sperre (Mutex) den GUI-Thread während der entsprechenden Zeit einfrieren, was wahrscheinlich einer der Ursachen für das Lag der Schieberegler war, bevor sie ihre Position aktualisierten,
- die Kettenanrufe an das
redraw
-Ereignis-Callback, durchgtk_widget_queue_draw()
, förderten “endlose” Stotterschleifen von (nutzlosen) Zwischen-Redraws, die anscheinend Menschen mit langsamen Computern mehr trafen als diejenigen mit Leistungsbiestern. Diese waren besonders schwer zu reproduzieren, je nach Hardware-Leistung, sodass man Foren finden kann, in denen Menschen davon überzeugt sind, dass Darktable die langsamste Software überhaupt ist, während andere von exzellenter Leistung berichten.
Also habe ich die gesamte Logik korrigiert durch:
- das
redraw
-Callback dumm zu machen (es zeichnet das Pixmap-Buffer, das verfügbar war, bedingungslos), - explizite Pipeline-Neuberechnungen im Modul- und Verlaufs-Code zu behandeln, wobei die Pipeline-Neuberechnungen nach Abschluss der Pipeline eine Widget-Neuzeichnung verlangen (ja, das ist mehr Code und mühsam, aber jetzt können Sie Neuberechnungen manuell optimieren - Leistung zählt),
- die spezielle Behandlung von “doppelten” Verlaufselementen zu entfernen (führte zu etwas Verschmutzung beim Umgang mit Masken, dies muss später behoben werden).
Könnte man denken, das war ein gelöstes Problem und ein gut gemachter Job, aber das lässt Darktable’s Genies aus der Gleichung.
Sehen Sie, die crop und perspective-Module sind besondere Module: Wenn Sie sie öffnen, wird ein “Bearbeitungsmodus” aktiviert, der das Zuschneiden deaktiviert und das vollständige Bild anzeigt. Dies ist erforderlich, um den Zuschneide-Rahmen (oder andere Positionierungen) von der Hauptvorschau über das gesamte Originalbild zu ziehen. Problem ist, es gab keinen expliziten Weg, eine Neuberechnung der Pipeline anzufordern… außer einen neuen Verlaufseintrag hinzuzufügen. Also fügten die Module ein gefälschtes Verlaufselement hinzu (später zurückgesetzt), um die Pipeline ungültig zu machen und die gtk_widget_queue_draw()
-Funktion aufzurufen. Aber das verschmutzte dann den Verlaufsstapel mit “leeren” Schritten, also fügte ein anderer Typ einen speziellen Behandlungssfall hinzu, der Verlaufs-Schritte zusammenführte, wenn keine Parameteränderungen auftraten. Aber der Verlaufsstapel (vom history-Modul, wie er in der Datenbank gespeichert ist) folgt nicht dem Undo/Redo-Verlaufsstapel, was bei den Benutzern zu Missverständnissen führte, was Undo/Redo tatsächlich tut.
Und das, meine Damen und Herren, ist, wie ein schreckliches Design noch schrecklicheres Design fördert, in einer endlosen Spirale des Wahnsinns.
Denken Sie daran, dass alles von der Notwendigkeit herrührt, den Kill-Switch der Pipeline zu aktivieren, damit Sie eine Neuberechnung in der Mitte unterbrechen können, wenn Sie wissen, dass deren Ergebnis sowieso verworfen wird. Dafür musste ich die Neuberechnung außerhalb des Gtk-Codes anordnen und überall dort aufrufen, wo es erforderlich war. Aber dann musste ich die Pipeline-Aktualisierungslogik in den crop, perspective and rotation, liquify und borders-Modulen neu verkabeln, und ich muss immer noch retouch (was das größte Fiasko ist) reparieren.
Abgesehen davon, dass es klarer zu lesen ist und die Möglichkeit bietet, die Aufrufe zu optimieren, startet die aktuelle Logik auch die Pipeline außerhalb des GUI-Threads, ohne zu warten, dass Gtk bitte die Zeit findet, den Rahmen neu zu zeichnen. Wie gewohnt werden Leute mit verrückten CPUs wenig bis keinen Nutzen in puncto Leistung bemerken, was wahrscheinlich der Grund ist, warum dies bei Darktable-Team gar kein Problem ist.
Episode 6: die Zinsen der technischen Schuld zurückzahlen
Also, an diesem Punkt hatte ich Neuberechnungen der Pipeline in den Modulen und GUI-Steuerelementen explizit gemacht und sie sparsam verteilt (was der Vorteil ist, sie explizit zu verteilen). Und dennoch, bemerkte ich, dass das Spielen mit Modulen, die spät in der Pipeline kommen, langsam war. Tatsächlich zeigte der Start von ansel -d perf
, dass
die gesamte Pipeline, angefangen beim Demosaicing-Modul, neu berechnet wurde, obwohl ich mit einem späten Modul interagierte, das seine Eingabe von color balance nahm.
Darktable hat seit jeher einen Pixel-Cache. Er speichert im Grunde die Zwischenzustände des Bildes, zwischen den Modulen. Wenn also Neuberechnungen der Pipeline, die viel unterhalb des aktuellen Moduls starten, bedeutete das, dass er größtenteils sinnlos war. Es stellte sich heraus, dass der Cache nur 8 Cache-Linien verwendet hat, was die heute verrückten Mengen an RAM wirklich unternutzt. Aber das auf 64 zu erhöhen half nicht bei Cache-Misses: der Cache war immer noch größtenteils nutzlos und der größte Teil der Pipeline wurde immer noch neu berechnet.
Wir müssen hier eine Pause machen. Selbst ein Maschinenbauingenieur ohne richtige Programmierausbildung wie ich weiß, was ein LRU-Cache ist:
- Sie erstellen eine feste Liste von Steckplätzen (Cache-Linien),
- wenn Sie etwas zu cachen haben, weisen Sie einem dieser Steckplätze einen Speicherpuffer einer zuvor bekannten Größe zu und weisen ihm eine eindeutige Kennung zu. Das könnte ein Prüfwert, ein zufälliger Hash oder sogar ein Zeitstempel sein, es muss nur immer auf die gleiche Weise erstellt werden und zu etwas Einzigartigem führen,
- wenn Sie Daten mit einer eindeutigen Kennung benötigen, fragen Sie die Liste der Steckplätze ab und suchen, ob diese Kennung bekannt ist:
- wenn ja, holen Sie ihren zugehörigen Puffer,
- wenn nicht:
- wenn Sie noch leere Steckplätze haben, erstellen Sie den zugehörigen Puffer und kopieren die Daten für die spätere Nutzung,
- wenn nicht, leeren Sie den ältesten Steckplatz und verwenden ihn erneut, um Ihre neuen Daten zu hosten.
In diesem Prozess müssen Sie nur die Größe der Puffer und die Kennungen kennen. Es ist sehr allgemein, Sie können alles cachen, sogar verschiedene Objekte, Ihr Cache muss sich nicht mit dem Inhalt auseinandersetzen, nicht einmal damit, wie die Kennungen generiert werden. Es ist sauber, es ist elegant, es ist unaufdringlich, es ist generisch, ich würde ihm mein Leben anvertrauen, weil es weitaus robuster ist als jedes Sicherheitssystem, das Sie in modernen Autos finden.
Also, wenn etwas so Einfaches nicht funktioniert, dann ist es meist, weil jemand etwas “Schlaues” versucht hat und gescheitert ist. Was das Darktable-Team in diesem Fall normalerweise tut, ist, sich durch alle pathologischen Eckfälle mit switch case
zu manövrieren und es zu etwas noch Komplexeren zu machen (indem sie alle Ausnahmen manuell mit Heuristiken behandelt), nur um sicherzustellen, dass später niemand eine Chance hat, die Ursache des Fehlers zu finden.
Zum Beispiel wurden Versuche unternommen, die Priorität der Cache-Linien neu zu gewichten, um sicherzustellen, dass das Modul vor demjenigen, das aktuell in der GUI bearbeitet wurde, zwischengespeichert wurde. Nicht nur funktionierte das nicht, sondern es verstärkte die Verbindungen zwischen Pipeline-Code und GUI-Code in einer Weise, die nicht einmal thread-sicher war (was der Grund war, warum es nicht funktionierte). GUI-Sachen sollten am Eingang und am Ausgang der Pipeline-Berechnungen geschehen, nicht dazwischen, weil wieder andere Threads, aber es verletzt auch das Modularitätsprinzip (halte Programmebenen so weit wie möglich getrennt und geschlossen), und diese Software muss aufhören, alles von allem abhängen zu lassen.
Erneut dauerte es mich 8 Monate, einschließlich obligatorischer Pausen von dieser totalen Scheißshow, um zum Grund des Problems auf eine Weise zu gelangen, die zu einer vereinfachenden Lösung führt. Und ich werde die Befunde auf lineare Weise präsentieren, wie eine Geschichte, aber bedenken Sie, ich begann, Dinge auf unscharfe und zufällige Weise zu entdecken, weil sie überall im Quellcode verstreut sind, also wird es weniger unordentlich erscheinen, als es tatsächlich war.
Wir beginnen mit der eindeutigen Kennung. Was stellt wirklich den Zustand eines Moduls auf eine einzigartige Weise dar? Nun, eine “kryptographische” Prüfsumme seiner internen Parameter. Cool, also hatte Darktable das lange implementiert. Außer dass es die Modul-Instanznummer nicht berücksichtigte und mit allen Arten von if
umging. Nicht vollständig, nicht robust, nicht einmal erforderlich. Hash alles, der Hash wird den Zustand der Variablen darstellen.
Ja, aber Module können neu angeordnet werden, also wie kümmern wir uns um die Reihenfolge in der Pipeline? Nun, Sie nehmen alle Hashes aller Module in der Reihenfolge der Pipeline und beginnen, sie linear zu summieren. Großartig. Außer Darktable hatte tatsächlich 2 davon, einen für GUI-Zwecke, der vom Ende der Pipeline aus begann (also in umgekehrter Reihenfolge), einen für Pipeline-Zwecke, in der Reihenfolge der Pipeline aber unzugänglich von der GUI (zum Beispiel… um ein Histogramm zu erhalten), und erneut, beide vermischten dies mit allen möglichen Prüfungen, um Sonderfälle zu Handhaben (Farb Pipette, Maskenvorschau, etc.).
Ganz zu schweigen davon, dass der interne Zustand des Moduls sich nicht ändert, ob Sie nun in der Großvorschau oder im Navigations-Thumbnail sind, im Dunkelkammer-Modus. Und dennoch wurde die Prüfziffer vollständig zweifach berechnet, einmal für jede Pipeline. Eigentlich, machen Sie es viermal, da es auch den GUI-Prüfsumme gibt (mostly für perspective und retouch-Module verwendet).
Und, last but not least, wenn man in den Darkroom hineinzoomt, wird nur der sichtbare Teil des Bildes (die Region Of Interest, auch ROI genannt) berechnet, was bedeutet, dass wir in unserem Cache-Mechanismus festhalten müssen, wo wir uns im Bild befinden. Aber das wurde völlig bei der Prüfziffer ausgelassen. Großer Fehler hier und alt.
Also, wie hat Darktable immer noch geschafft “zu funktionieren”, fragen Sie vielleicht?
Nun, indem es den Cache mehr oder weniger vollständig löschte, bei jeglicher pathologischen Operation: Zoom, Schwenk, Maskenvorschau, Farb Pipette, das Aktivieren/Deaktivieren des Bearbeitungsmodus crop und perspective-Module. Das ist eine Methode, mit Consistenz umzugehen, ohne mit Konsistenz umzugehen: alles niederbrennen. Es machte es größtenteils sinnlos, wie die sehr niedrigen Cache-Treffer-Statistiken zeigen (einfach ansel -d dev
starten, um es zu zeigen).
Wie habe ich das Problem gelöst?
- Wenn ein neuer Modul-Verlaufs-Eintrag hinzugefügt wird, wird die Prüfziffer der Parameter berechnet, indem Parameter, Masken, Mischungsoptions, Instanznummer, Reihenfolge in der Pipeline etc. berücksichtigt werden. Das bedeutet, dass alle Pipelines hier die gleiche Prüfziffer/ID teilen (möglicher zukünftiger Nutzen wäre, sie in der Datenbank zu speichern),
- Bevor eine Pipeline berechnet wird, berechnen wir die globale Prüfziffer aller Module, von Anfang bis Ende, unter Berücksichtigung des Maskenanzeigestatus, der Prüfziffer der vorhergehenden Module und des ROI (Größe und Koordinaten). Diese Prüfziffer kann später, ohne zusätzliche Berechnungen, direkt abgerufen werden.
- Der Cache beschäftigt sich mit dieser globalen Prüfziffer und nur damit. Keine
ifs
, keinebuts
, keine Heuristiken, keine Bedingungen, keine Umgehungslösungen. - Module können einen Cache-Bypass anfordern, z. B. beim Verwenden der Farb Pipette. Dies verseucht spätere Module in der Pipeline, bevor die Pipeline berechnet wird, sodass der zustandslose Status frühzeitig bekannt ist und die vorgelagerten Module nicht beeinflusst. Das sollte nur eine Umgehungslösung sein, bevor die Farb Pipetten tatsächlich Cache-Linien direkt nutzen können und könnte für zukünftige Module wiederverwendet werden, die nicht-standardmäßige Dinge tun (malen?).
Vorteile:
- Die modulweise, interne, Prüfziffer wird einmal für alle Pipelines berechnet,
- Da die pipelineweise, globale, Prüfziffer jedes Moduls bekannt ist bevor die Pipeline-Neuberechnung startet:
- kann sie auch für die GUI-Synchronisierung verwendet werden, also habe ich beide Darktable-Prüfziffern in eine zusammengeführt,
- ist sie innerhalb des Bereichs der Pipeline konstant, sodass Cache-Linien zwischen mehreren Pipelines (z.B. Demosaiking und Rauschunterdrückung) mit begrenzten Thread-Sperrproblemen geteilt werden können1,
- Module, die seltsame Dinge tun, haben einen einheitlichen und vorhersehbaren Weg, einen Cache-Bypass von GUI-Ereignissen anzufordern, wenn sie ihn benötigen.
Diese Logik ist nicht nur effizienter (weniger Berechnungen), sie ist auch einfacher und kann für interessante Funktionen erweitert werden. Aus der Perspektive des Cache behandeln wir mit nichts anderem als einer Prüfziffer, jeder Modulzustand von Interesse ist darin gefaltet.
Aber, was noch wichtiger ist, der Cache ist endlich nützlich, besonders wenn man in der Bearbeitungsgeschichte vor- und zurückgeht, undo/redo
verwendet oder Module aktiviert/deaktiviert. Die allgemeine Reaktionsfähigkeit der GUI ist viel besser.
Ich bin sicher, es gibt unentdeckte Mängel und Details, die ich vergessen habe, an die neue Logik anzuschließen, und das retouch-Modul ist immer noch größtenteils kaputt, aber die Anpassung an etwas so Einfaches sollte machbar sein.
Unterdessen in Darktable 4.8
- Die Pipelines-Prüfziffer wird während der Pipeline-Laufzeit berechnet, sodass sie außerhalb unbekannt ist,
- Aufgrund dessen haben sie die GUI-gegen-Pipeline-Prüfziffern nicht dedupliziert… viel Glück dabei, Inkonsistenzen zwischen beiden in Zukunft zu verfolgen,
- Ihr Cache-Handling-Code ist mehr als doppelt so groß wie meiner und verwendet Heuristiken (über den Art der Pipeline, die Art des Moduls, den Zustand der Maskenanzeige, die Verwendung des Farb Pipette und manuell in Modulen definierte Caching-Hinweise), um Probleme zu umgehen. Der Cache ist nicht mehr inhaltsagnostisch und viel Glück, diese Spaghetti zu debuggen.2
- Sie berechnen immer noch die Modulparameter (intern) Prüfziffer zweimal, einmal für jede Pipeline,
- Es hat ihnen fast 2 Jahre gedauert, um dorthin zu gelangen (seit der Veröffentlichung von 4.0),
- Ich würde gerne ihre Cache-Treffer/Miss-Statistiken sehen (möchte ich mein PTSD wiederbeleben, indem ich diese Software jemals wieder öffne? Ich passe, danke).
- Menschen, die denken, dass mehr Affen, die mit den Händen in der Luft wedeln, bessere Qualität garantieren, sollten aufhören zu denken.
Schlussfolgerung
Die Menge an verbrachter Zeit und kürzlich gebrochenen Dingen, die zu beheben sind, um dorthin zu gelangen, war wirklich unerträglich, aber es wurde durch Code verstärkt, der nicht modular zusammengeworfen wurde, ohne klare Unterscheidung, was zur (G)UI gehört, was zum Backend, was zu Modulverläufen und was zu Pipelineschnittstellen gehört. Das Cache-Ding hat nur 8 Monate gedauert, meistens Archäologie und Reverse-Engineering, zusätzlich zu dem, was bereits in GUI-Steuerelementen und expliziten Pipeline-Neuberechnungen getan wurde.
Es gibt noch Probleme zu lösen:
- die Anzahl der verfügbaren Cache-Linien ist eine Benutzerpräferenz und überprüft nicht den noch verfügbaren Speicherplatz auf dem Gerät,
- das Histogramm/Scopes-Modul ist größtenteils durch Design kaputt, weil es durch spezielle Heuristiken (jetzt entfernt) in einem Modul behandelt wurde, das in der GUI unsichtbar ist (
gamma.c
). Die neue Logik macht es möglich, es zwangsweise zu cachen und die Cache-Linie aus dem GUI-Thread abzurufen. - interne Histogramme des Moduls werden beim Betreten der Dunkelkammer nicht sofort gezeichnet,
- die Handhabung der Farb Pipette könnte vereinfacht und eleganter gemacht werden,
- die Handhabung der Geschichte hat immer noch einige Eckfälle.
Jedoch, da ich mich weigere, etwas zu “reparieren”, wenn meine Korrektur die Dinge nicht einfacher macht, beginnt diese Strategie sich auszuzahlen, da der Code viel linearer ist, mit weniger Fällen zu testen und letztendlich etwas schneller ist. Da ich Fortschritte mache, wird er langsam lesbarer und verbesserbar. Natürlich bricht man mit dem Kern der Software in diesem Maße natürlich etwas (was nicht kaputt gehen sollte, wenn der Code modular wäre).
Hier stellt sich die berechtigte Frage: Warum sich die Mühe machen, Ansel/Darktable abzulehnen und sich nicht mit etwas Besserem, Schnellerem und Glänzenderem (wie Vkdt) weiterbewegen? Nun, Vkdt (oder irgendetwas anderes Neues) wird ein grober Prototyp bleiben, der mit anderen groben Prototypen konkurriert (das ist Open Source in a nutshell), Jahre entfernt von einem allgemein verwendbaren Produkt. Ein weiteres unfertiges/halb-assed Prototypen zur Landschaft hinzuzufügen, wird nicht gut tun. Es wäre schön, etwas nicht schmuddeliges und ziemlich fertigzustellen, für eine Veränderung. Außerdem ist der (sehr) alte Code von Darktable sauber und stabil (nun, größtenteils), es sind nur die letzten Jahre, die sich zum Schlechtesten gewendet haben. git blame
zeigt immer die gleichen 3 Namen auf den wirklich beschissenen Zeilen, bis zu dem Punkt, wo ich manchmal automatisch die entsprechenden Zeilen lösche, wenn ich gesehen habe, wer sie geschrieben hat, aus Gewohnheit.
Es besteht auch die Befürchtung, dass, egal wie schnell Vulkan Vkdt macht, das, was Darktable wirklich beschissen macht, schlechte Entscheidungen, schlechte Prioritäten, Programmierfehler, nicht gelernte Lektionen sind, und wenn diese Fehler über Vkdt reproduziert werden, könnte es länger dauern, die Konsequenzen zu erkennen, mit mehr Rechenleistung, aber schließlich werden die Dinge den gleichen Weg gehen. Mehr Ressourcen zu haben macht es erschwinglicher, dumm zu sein… bis es das nicht mehr tut, und Sie erkennen, wie gefangen Sie sind.
Translated from English by : ChatGPT. In case of conflict, inconsistency or error, the English version shall prevail.
The source code actually has a 10-years-old
TODO
comment detailing how to do that. ↩︎It should be noted that “my” cache code is actualy pretty much how Roman Lebedev and Johannes Hanika wrote it 10 years ago. I simplified a couple of things, mostly removing stuff added since then, and added nothing of my own, because it’s a Garbage In/Garbage Out situation where you should rather clean your input rather than trying to handle any corner case internally through unlegible heuristics. ↩︎