Wir bringen die transaktionale Speichersemantik von Verse zu C++

Phil Pizlo

15. März 2024
Ab Fortnite Version 28.10 werden einige unserer Server – große C++-Executables, die auf der Unreal Engine basieren, – mit einem neuen Compiler kompiliert, der C++ transaktionale Speichersemantik ermöglicht, die mit Verse kompatibel ist. Das ist ein erster Schritt dazu, noch mehr Unreal Engine-Funktionen in Verse umzusetzen, ohne die Verse-Semantik zu verändern.

Die Programmiersprache Verse verfügt über transaktionalen Speicher als Feature erster Klasse. Hier ist ein Beispiel: 

var X = 0
if:
    set X = 42      # wird zurück gerollt wegen der
                    # nächsten Zeile
    X > 100         # schlägt fehl und löst ein Rollback aus
X = 0               # X ist dank des Rollbacks wieder 0

Dieses Verhalten gilt für jeden Effekt, nicht nur für die set-Operation in Verse. Beispielsweise hätten wir stattdessen eine C++-Funktion abrufen können, die mit C++ eine veränderliche Variable modifiziert (also z. B. in einen Zeiger speichert), und hätten dann andere C++-Funktionen benutzen können, um die veränderliche Variable zu lesen:

SetSomething(0)            # implementiert in C++, legt eine C++
                           # Variable fest
if:
    SetSomething(42)       # wird zurück gerollt wegen der
                           # nächsten Zeile
    GetSomething() > 100   # schlägt fehl und löst einen Rollback aus
GetSomething() = 0         # ist dank des Rollbacks wieder 0

Um die Verse-Semantik einzuhalten, muss C++ also die Transaktionsregeln von Verse einhalten, indem es sich alle Effekte merkt, damit sie zurückgesetzt werden können.

Verse-Code wird immer innerhalb einer Transaktion ausgeführt und Transaktionen werden entweder erfolgreich abgeschlossen oder abgebrochen. Transaktionen, die abgebrochen werden, sind "unsichtbar" – all ihre Effekte werden rückgängig gemacht, und es ist so, als wäre nichts passiert. Abgesehen von den Auswirkungen auf die Gleichzeitigkeit gilt für das Transaktionsmodell von Verse Folgendes:
  • Ein Laufzeitfehler in Verse sollte die gesamte Transaktion abbrechen, da Verse es nicht zulässt, dass nicht erfolgreiche Transaktionen bestätigt werden.
  • Verse-Fehlerkontexte, wie die Bedingung einer if-Anweisung, sind verschachtelte Transaktionen, die bestätigt werden, wenn die Bedingung erfolgreich ist, und andernfalls abgebrochen werden. Bedingte Ausdrücke dürfen Effekte haben. Wenn die Bedingung fehlschlägt, ist es so, als ob die Effekte nie eingetreten wären.
  • Alle Effekte werden transaktionalisiert, auch die, die im nativen Code auftreten. Wenn ein Verse-Programm C++ auffordert, etwas zu tun, und dann in derselben Transaktion auf einen Laufzeitfehler stößt, muss alles, was C++ getan hat, rückgängig gemacht werden. Wenn ein Verse-Programm C++ auffordert, etwas aus einem Fehlerkontext heraus zu tun, und die Bedingung dann fehlschlägt, muss alles, was C++ getan hat, ebenfalls rückgängig gemacht werden.
  • Verse-Transaktionen sind grobkörnig. Verse-Code befindet sich standardmäßig immer in einer Transaktion, und der Umfang dieser Transaktion wird vom äußersten Aufrufer von Verse bestimmt. Da die meisten Verse-APIs in C++ implementiert sind, bedeutet das, dass der Abbruch einer Verse-Transaktion fast immer dazu führt, dass einige C++-Statusänderungen rückgängig gemacht werden müssen.

Die erste Verse-Implementierung hat dieses Problem folgendermaßen gelöst:
  • Aufschiebung der Verse-Laufzeitfehlersemantik. Momentan setzen Verse-Laufzeitfehler die abgebrochene Transaktion nicht korrekt zurück. Das ist etwas, das wir beheben müssen.
  • Verbieten, dass mehrere APIs in einem Fehlerkontext aufgerufen werden, entgegen der Verse-Semantik. Das wird in der Sprache als <no_rollback>-Effekt bezeichnet, den wir abschaffen werden, sobald dieses Problem behoben ist.
  • C++-Code, der unterstützt, dass Transaktionen manuell Undo-Handler für alle von ihm durchgeführten Effekte registrieren. Das ist so kompliziert, dass es nicht skalierbar (daher der <no_rollback>-Effekt) und fehleranfällig ist, was zu Abstürzen führt, die schwer zu debuggen sind, etwa wenn ein Undo-Handler für etwas registriert ist, das gelöscht wird.

Ab Fortnite Version 28.10 werden einige unserer Server mit einem neuen Clang-basierten C++-Compiler kompiliert, den wir als AutoRTFM bezeichnen (Automatic Rollback Transactions for Failure Memory). Dieser Compiler transformiert C++-Code automatisch so, dass er Undo-Handler für jeden Effekt (einschließlich aller Speicheranweisungen) genau registriert, damit sie im Falle eines Verse-Rollbacks rückgängig gemacht werden können. AutoRTFM erreicht das ohne Performance-Overhead für C++-Code, der nicht innerhalb einer Transaktion ausgeführt wird. Das trifft sogar dann zu, wenn dieselbe C++-Funktion sowohl innerhalb als auch außerhalb von Transaktionen benutzt wird; Overhead tritt nur dann ein, wenn der laufende Thread innerhalb eines Transaktionsumfangs ist. Und obwohl ein Teil des C++-Codes modifiziert werden muss, um innerhalb von AutoRTFM zu funktionieren, mussten wir insgesamt nur 94 Code-Änderungen an der Fortnite-Codebase vornehmen, um sie an AutoRTFM anzupassen. Dabei sind selbst diese Änderungen – wie wir in diesem Post zeigen werden – schockierend einfach.

In diesem Beitrag sehen wir uns genau an, wie dieser Compiler und seine Laufzeit funktionieren, und machen ein paar Prognosen hinsichtlich unserer Vision, wie sich das auf das Verse-Ökosystem auswirken wird.

AutoRTFM-Compiler und -Laufzeit

AutoRTFM ist darauf ausgelegt, es einfach zu machen, bestehenden C++-Code – der nie dazu gedacht war, über transaktionale Semantik zu verfügen – mithilfe eines alternativen Compilers transaktional zu machen. Beispielsweise ermöglicht AutoRTFM es Ihnen, Code wie diesen zu schreiben:

void Foo()
{
    TArray
<int> Array = {1, 2, 3};
    AutoRTFM::Transact([&] ()
    {
        
Array.Push(42);
        // Das Array ist hier {1, 2, 3, 42}.
        AutoRTFM::Abort();
    });
    // Aufgrund des Abort ist das Array hier {1, 2, 3}.
}

Das funktioniert ohne Änderungen an TArray. Dies funktioniert sogar dann, wenn TArray seinen Sicherungsspeicher innerhalb von Push neu zuweisen muss. TArray ist in dieser Hinsicht nicht einmal besonders; AutoRTFM kann das für std::vector/std::map sowie eine Vielzahl anderer Datenstrukturen und Funktionen in der Unreal Engine und der C++-Standardbibliothek tun. Die einzigen hierfür nötigen Änderungen sind die Integration von AutoRTFM mit Low-Level-Allokatoren und dem Garbage Collector (GC) – und wie wir in diesem Abschnitt zeigen werden, sind selbst diese Änderungen geringfügig (sie sind lediglich ergänzend und es handelt sich um nur wenige Zeilen; wir haben sie zu den vielen mallocs der Unreal Engine, zu Unreal Engines GC und zum malloc/new-System hinzugefügt). Darüber hinaus erreicht AutoRTFM das komplett ohne Performance-Verschlechterung für die Codepfade, die außerhalb einer Transaktion (also außerhalb des dynamischen Bereichs eines Transact -Aufrufs) ausgeführt werden. Selbst wenn ein Codepfad (wie TArray und viele andere) innerhalb von Transaktionen verwendet wird, fallen für diese Verwendung außerhalb von Transaktionen keine Kosten an. Wenn Code innerhalb einer Transaktion ausgeführt wird, ist der Overhead groß (einige interne Benchmarks zeigten eine vierfache Erhöhung), aber nicht groß genug, um die Tick-Rate des Fortnite- Servers zu beeinträchtigen.

In diesem Abschnitt erklären wir, wie das möglich ist. Zuerst beschreiben wir die allgemeine Architektur. Danach beschreiben wir den neuen LLVM-Compiler-Pass, der anstatt Code direkt zu instrumentieren, einen transaktionalen Klon des gesamten Codes erstellt, der möglicherweise in einer Transaktion involviert sein könnte, und dann nur diesen Klon instrumentiert. Anschließend beschreiben wir die Laufzeit und ihre API, die es einfach machen, AutoRTFM in bestehenden C++-Code zu integrieren (einschließlich der spaßigeren Teile wie Allokatoren). Die nächsten zwei Abschnitte beschreiben diese zwei jeweiligen Komponenten.
 

AutoRTFM-Architektur

Langfristig möchten wir transaktionalen Speicher nutzen, um Parallelität zu erreichen. Implementierungen von transaktionalem Speicher, die Parallelität erreichen, neigen dazu, einen Lese-/Schreibsatz zu erhalten, der den Snapshot des Heap-Status verfolgt, in dem die Transaktion ausgeführt wird. Aus diesem Grund müssten wir sowohl Schreib- als auch Lesevorgänge instrumentieren, da wir ansonsten nicht wissen würden, ob eine Transaktion, die nur einen bestimmten Ort gelesen hat, von einem Rennen durch gleichzeitige Commits betroffen war.

Kurzfristig müssen wir allerdings zwei Ziele erreichen:
  • Transaktionale Semantik für Verse-Code, selbst wenn dieser Verse-Code C++ aufruft. Das setzt keine Parallelität voraus, benötigt aber transaktionalen Speicher.
  • Die Normalisierung der Kompilierung des Fortnite- Servers– eines sehr großen und ausgereiften C++-Codes– mit einem neuen Compiler, der transaktionale Speichersemantik hinzufügt.

Aus diesem Grund ist unser erster Schritt nicht AutoSTM (Software Transactional Memory), sondern AutoRTFM (Rollback Transactions for Failure Memory), das lediglich Single-Threaded-Transaktionen implementiert. AutoRTFM kümmert sich um ein grundsätzlich einfacheres Problem. Da es keine Gleichzeitigkeit gibt (der Gameplay-Code, den Verse aufrufen würde, wird erwartungsgemäß bereits Main-Thread-Only sein), müssen wir nur Schreibvorgänge aufzeichnen. Das Ignorieren von Lesevorgängen bedeutet weniger Overhead beim Erststart sowie weniger Bugs, da sämtlicher Code, der lediglich den Heap liest, einfach in AutoRTFM funktioniert.

Die Architektur von AutoRTFM ist konzeptionell einfach. Die AutoRTFM-Laufzeit verwaltet ein Protokoll der Speicherorte und ihrer alten Werte. Der Compiler instrumentiert Code, um Funktionen in der AutoRTFM-Laufzeit aufzurufen, wann immer Speicher geschrieben wird. Die zwei nächsten Abschnitte nehmen den Compiler und dann die Laufzeit genauer unter die Lupe.
 

LLVM-Compiler-Pass

Die Aufgabe des AutoRTFMPass besteht darin, den gesamten Code so zu instrumentieren, dass er die transaktionale Semantik von Verse befolgen kann. In Fortnite kann derselbe Code jedoch auch an einigen Stellen verwendet werden, an denen sich kein Verse-Code im Stapel befindet und die Semantik von Verse unnötig ist, sowie an anderen Stellen, an denen sich Verse im Stapel befindet und ein Rollback möglich ist.

Anstatt Code wie "wenn der aktuelle Thread in einer Transaktion ist, dann Schreibvorgänge protokollieren" auszugeben, basiert der AutoRTFM-Compiler auf Klonen. Die Teilnahme an einer Transaktion wird vollständig durch die Ausführung im Transaktionsklon repräsentiert. Tatsächlich ist das Bit bCurrentThreadIsInTransaction im Programmzähler codiert. Aus der Sicht des Compilers ist das einfach: Er klont einfach Ihren gesamten Code, gibt den geklonten Funktionen verschlüsselte Namen und instrumentiert dann nur die Klone. Der Originalcode bleibt uninstrumentiert und führt keinerlei Protokollierung oder andere besondere Vorgänge durch, sodass für die Ausführung dieses Codes keine Kosten anfallen. Der geklonte Code versteht Instrumentierung und weiß definitiv, dass er sich in einer Transaktion befindet, weshalb wir sogar VM-ähnliche Tricks ausführen und etwa mit alternativen Aufrufkonventionen herumspielen können.

Da Code geklont wird, müssen wir auch indirekten Funktionscode in den Klonen instrumentieren. Wenn eine Funktion einen indirekten Aufruf ausführt, wird der Zeiger, den sie versuchen wird aufzurufen, ein Zeiger auf die ursprüngliche Version des Aufrufers sein. Aus diesem Grund fragen alle indirekten Aufrufe innerhalb des geklonten Codes die AutoRTFM-Laufzeit zuerst nach der geklonten Version dieser Funktion. Ebenso wie die gesamte Instrumentierung von AutoRTFM, macht das geklonten Code teurer, aber hat keine Auswirkung auf den Originalcode.

AutoRTFMPass wird am selben Punkt in der LLVM-Pipeline ausgeführt wie die Sanitizer – also nachdem alle IR-Optimierungen durchgeführt wurden und kurz bevor der Code an das Backend gesendet wird. Das hat einige wichtige Implikationen, etwa dass selbst in den instrumentierten Klonen nur die Ladungen und Speicherungen instrumentiert werden, die die Optimierung überlebt haben. Während also die Zuweisung an eine nicht-entkommende lokale Variable in C++ eine abstrakter "Speicherung" ist (den Clang beim Erstellen der nicht optimierten LLVM-IR für dieses Programm entsprechend darstellen würde), wird der Speicher komplett verschwunden sein, sobald AutoRTFMPass sein Ding macht. Vermutlich wird er durch den SSA-Datenfluss ersetzt werden, mit dem sich AutoRTFM nicht herumschlagen muss. Außerdem haben wir ein paar kleine Optimierungen implementiert, die auf bestehenden statischen LLVM-Analysen basieren, um möglichst keine Speicher zu instrumentieren, wenn wir nachweisen können, dass das nicht nötig ist.

Der Einsatz von Funktionsklonung bedeutet, dass wenn Sie C++-Code mit Ihrem Compiler kompilieren, aber niemals irgendeine AutoRTFM-Laufzeit-API aufrufen, die Klone niemals aufgerufen werden. Aus diesem Grund wird für Sie niemals Overhead entstehen, abgesehen von den Kosten für eine größere Binary mit totem Code (also nicht komplett null, aber fast).

Im nächsten Abschnitt sehen wir uns an, wie die AutoRTFM-Laufzeit die instrumentierten Klone zum Singen bringt.
 

Die AutoRTFM-Laufzeit

Die Laufzeit von AutoRTFM ist in den Kern der Unreal Engine integriert, sodass sie für Unreal Engine-Programme wie Fortnite immer verfügbar ist. Die wichtigste Funktion von AutoRTFM, die das ermöglicht, ist AutoRTFM::Transact. Wenn diese Funktion mit einem Lambda aufgerufen wird, richtet die Laufzeit ein transaktionales Objekt ein, das die Protokolldatenstrukturen und andere transaktionale Zustände enthält. Anschließend ruft Transact den Klon des Lambda auf. Er ist eine genaue Kopie des Lambda, aber verfügt über Transaktionsinstrumentierung und folgt dem AutoRTFM-API.

Momentan rufen wir Transact auf, wenn Verse in einen Fehlerkontext wie eine if-Bedingung eintritt. In Zukunft werden wir Transact aufrufen, noch bevor irgendeine Verse-Ausführung startet.

Die Laufzeit muss folgende Fälle verarbeiten und APIs zur Verarbeitung bereitstellen:
  • Was passiert, wenn das Lambda, das an Transact oder eine Funktion übergeben wird, die es transitiv aufruft, keinen Klon hat? Das passiert bei Systembibliotheksfunktionen und allen Funktionen von Modulen, die nicht mit AutoRTFM kompiliert wurden.
  • Was passiert, wenn der transaktionalisierte Code etwas ausführen muss, das nicht transaktionssicher ist – etwa wenn er Atomics benutzen muss, um einen Befehl zu einem physikalischen Befehlspuffer sperrenfrei hinzuzufügen? AutoRTFM bietet mehrere APIs für die manuelle Steuerung der Transaktion. Die am häufigsten ausgeführte Instanz dieses Problems sind malloc/free und ähnliche Funktionen (wie benutzerdefinierte Allokatoren).

Immer wenn AutoRTFM aufgefordert wird, eine geklonte Funktionssuche durchzuführen, und der Klon nicht gefunden wird, bricht AutoRTFM die Transaktion mit einem Fehler ab. Das bringt Sicherheit, da so ein versehentliches Aufrufen von nicht transaktionssicherem Code unmöglich wird. Der Aufgerufene wurde nämlich entweder transaktionalisiert und der Klon ist vorhanden und wird aufgerufen, oder der Aufgerufene ist es nicht und es entsteht ein Abbruch wegen einer fehlenden Funktion. Daher ist zum Beispiel der Aufruf von WriteFile oder Ähnlichem in einer Transaktion standardmäßig ein Fehler. Das ist ein wünschenswertes Standardverhalten, da WriteFile nicht zuverlässig rückgängig gemacht werden kann, denn selbst wenn wir später versucht hätten, den Schreibvorgang rückgängig zu machen, könnte ein anderer Thread (entweder in unserem Prozess oder einem anderen Prozess) den Schreibvorgang gesehen und auf dieser Grundlage etwas anderes getan haben, was nicht wiederherstellbar ist. Dies macht es praktisch, ein vorhandenes C++-Programm zu nehmen, es mit AutoRTFM zu kompilieren und zu versuchen, einen Transact durchzuführen. Das wird entweder ordnungsgemäß funktionieren, oder Sie erhalten eine Fehlermeldung, dass Sie eine nicht transaktionssichere Funktion aufgerufen haben, sowie einen Hinweis, wie Sie diese Funktion und ihre Aufrufstelle finden.

Der schwierigste Teil sind malloc und verwandte Funktionen. AutoRTFM verfolgt den Ansatz, die Implementierung von malloc nicht zu transaktionalisieren, sondern Aufrufer dazu zu zwingen, Aufrufe an malloc mit der AutoRTFM-API zu umschließen. Dies ist besonders einfach in Programmen wie der Unreal Engine, die bereits über eine Fassaden-API verfügen, die die eigentliche malloc-Implementierung umschließt. Sie können am besten verstehen, was wir tun, indem Sie sich den Code ansehen. Vor AutoRTFM sah FMemory::Malloc in etwa so aus:
 
void* FMemory::Malloc(SIZE_T Count, uint32 Alignment)
{
    
return GMalloc->Malloc(Count, Alignment);
}


Beachten Sie, dass wir einige Details entfernt haben, um nur das Wichtige hervorzuheben.

Mit AutoRTFM tun wir stattdessen Folgendes:
 
void* FMemory::Malloc(SIZE_T Count, uint32 Alignment)
{
    
void* Ptr;
  
  UE_AUTORTFM_OPEN(
    {
        Ptr =
GMalloc->Malloc(Count, Alignment);
    });
    AutoRTFM::
OnAbort([Ptr]
    {
        
Free(Ptr);
    });
    
return AutoRTFM::DidAllocate(Ptr, Count);
}


Sehen wir uns Schritt für Schritt an, was passiert.
  1. Wir verwenden UE_AUTORTFM_OPEN um anzugeben, dass der eingeschlossene Block (der in ein Lambda umgewandelt wird) ohne Transaktionsinstrumentierung ausgeführt werden soll. Das ist für die Laufzeit und den Compiler einfach zu implementieren: Es bedeutet lediglich, dass wir einen Aufruf an die ursprüngliche (nicht geklonte) Version des Lambda senden. Es bedeutet, dass GMalloc->Malloc innerhalb der Transaktion ausgeführt wird – allerdings ohne transaktionale Instrumentierung. Wir nennen das eine "offene Ausführung" (im Gegensatz zur geschlossenen Ausführung, bei der es sich um die Ausführung des transaktionalisierten Klons handelt). Nachdem UE_AUTORTFM_OPEN zurückgegeben wurde, wäre der malloc-Aufruf also sofort vollständig ausgeführt worden, ohne dass Zurücksetzungen registriert worden wären. Auf diese Weise bleibt die Transaktionssemantik erhalten, da malloc bereits Thread-sicher ist. Daher ist die Tatsache, dass dieser Thread einen malloc ausgeführt hat, für andere Threads erst sichtbar, wenn der Zeiger auf das malloc-Objekt die Transaktion verlässt. Der Zeiger kann erst nach einer Bestätigung aus der Transaktion austreten, da die einzigen Orte, an denen Ptr gespeichert werden kann, entweder anderer neu zugewiesener Speicher oder Speicher sind, den AutoRTFM durch Protokollierung des Schreibvorgangs transaktionalisiert.
  2. Als Nächstes registrieren wir einen On-Abort-Handler, um das Objekt mithilfe von AutoRTFM::OnAbort freizugeben. Wenn die Transaktion bestätigt wird, wird dieser Handler nicht ausgeführt, sodass das mallocierte Objekt überlebt. Wenn die Transaktion jedoch abbricht, geben wir das Objekt frei. Das sorgt dafür, dass es bei Abbrüchen nicht zu Speicherlecks kommt.
  3. Abschließend informieren wir AutoRTFM darüber, dass dieser Speicherabschnitt neu zugewiesen wurde, was bedeutet, dass die Laufzeit keine Schreibvorgänge in diesen Speicher protokollieren muss. Wenn keine Schreibvorgänge in den Speicher protokolliert werden, ist es einfacher, OnAbort auszuführen, da wir uns keine Sorgen darüber machen müssen, dass AutoRTFM versuchen wird, den Inhalt des Objekts wieder in einen früheren Zustand zu überschreiben, nachdem wir es bereits freigegeben haben. Außerdem ist es eine nützliche Optimierung, keine Schreibvorgänge in neu zugewiesenen Speicher zu protokollieren.

Wenn dieser Code außerhalb einer Transaktion ausgeführt wird (d. h. wenn der gesamte Code offen aufgerufen wurde), dann:
  • Führt UE_AUTORTFM_OPEN nur den Codeblock aus.
  • Ist AutoRTFM::OnAbort ein No-Op; das übergebene Lambda wird einfach ignoriert.
  • AutoRTFM::DidAllocate ist ein No-Op.

Als Nächstes sehen wir uns an, was im free passiert. Wir haben auch hier einige Details entfernt, die für diese Diskussion uninteressant sind.

void FMemory::Free(void* Original)
{
  
  UE_AUTORTFM_ONCOMMIT(
    {
        
GMalloc->Free(Original);
    });
}

 
Die Funktion UE_AUTORTFM_ONCOMMIT weist dieses Verhalten auf:
  • In transaktionalisiertem Code: Registriert einen On-Commit-Handler, um das angegebene Lambda offen aufzurufen. Wenn die Transaktion also bestätigt wird, wird das Objekt freigegeben. Es wird nicht sofort freigegeben. Wenn die Transaktion abgebrochen wird, wird das Objekt nicht freigegeben.
  • Im offenen Code: Führt das Lambda sofort aus, sodass das Objekt sofort freigegeben wird.

Dieser Stil der Instrumentierung – malloc malloct sofort, aber bricht bei einem free ab, während free das free bestätigt – wird auf all unsere Allokatoren angewandt. Die AutoRTFM-Laufzeit installiert sogar ihre eigenen Handler für malloc/free/new/delete durch Funktionssuche- und Compiler-Hacks, sodass Sie das richtige Verhalten erhalten, selbst wenn Sie Systemallokatoren verwenden. Der Unreal Engine GC macht etwas Ähnliches, allerdings wird keine free-Funktion verwendet und wir führen GC-Schritte am Ende des Level-Ticks aus (also außerhalb aller Transaktionen).

Die Kombination aus Compiler, Laufzeit und der malloc-/GC-Instrumentierung, die wir dank der Open/OnCommit/OnAbort APIs erhalten, bietet uns alles, was wir zum Ausführen von C++-Code in einer Transaktion benötigen, solange dieser Code keine Atomics, Sperren, Systemaufrufe oder Aufrufe von Teilen der Unreal Engine-Codebasis verwendet, die wir überhaupt nicht transaktionalisiert haben (wie derzeit die Rendering- und Physik-Engines). Dies hat zu insgesamt 94 Anwendungen der Open APIs in Fortnite geführt. In diesen Anwendungen wird vorhandener Code mithilfe von Open und seinen Freunden in einer geeigneten Granularität umschlossen, anstatt Algorithmen oder Datenstrukturen zu verändern.

Auswirkungen auf das Verse-Ökosystem

AutoRTFM ist ein erster Schritt, um mehr Verse-Semantik in der UEFN einzuführen. Mit AutoRTFM können wir:
  • Ein saubereres Laufzeitfehlerverhalten implementieren, damit ein Spiel selbst dann weiter ausgeführt werden kann, wenn Fehler auftreten. AutoRTFM ermöglicht es uns, einen bekannten Status wiederherzustellen, selbst wenn der Laufzeitfehler tief im C++-Code festgestellt wurde.
  • Entfernen Sie den <no_rollback> -Effekt, damit mehr Code aus if und for aufgerufen werden kann. Dieser Effekt ist nur deshalb vorhanden (und verschmutzt viele Funktionssignaturen), weil C++-Code meistens nicht transaktional sein kann. AutoRTFM behebt dieses Problem.
  • Die globale Transaktionssemantik offenlegen, damit Datenbankzugriffe nur als Verse-Code und globale Variablen geschrieben werden können. AutoRTFM bedeutet, dass Verse-Code spekulativ geschrieben werden kann. Das bietet einige aufregende Möglichkeiten wie die Einbindung von Verse in eine Transaktion, die sich über das Netzwerk erstreckt, da wir eine Spekulation durchführen können werden, um die Kosten von Ferndatenzugriffen zu amortisieren.

Sichern Sie sich die Unreal Engine noch heute!

Holen Sie sich das offenste und fortschrittlichste Erstellungswerkzeug der Welt.
Die Unreal Engine wird mit allen Funktionen und vollem Zugriff auf den Quellcode geliefert und ist sofort einsatzbereit.