C++, Rust und Hochfrequenzhandel: Wo deterministische Latenz über das Argument entscheidet

C++, Rust und Hochfrequenzhandel: Wo deterministische Latenz über das Argument entscheidet

C++, Rust und Hochfrequenzhandel: Wo deterministische Latenz über das Argument entscheidet

Einführung

Debatten über Programmiersprachen werden normalerweise toleriert, da sich die meisten Systeme ein wenig Theater leisten können. Ein Dienst ist etwas ineffizient, eine Warteschlange wird länger als sie sollte, eine Wiederholungsrichtlinie bewirkt etwas moralisch Fragwürdiges und alle machen weiter, weil das Produkt immer noch funktioniert, die Einnahmen immer noch ankommen und das Latenzdiagramm auf eine überlebensfähige Weise hässlich ist.

Der Hochfrequenzhandel ist weniger sentimental. Es ist egal, welche Sprache in diesem Quartal das Internet gewonnen hat. Es ist wichtig, ob Marktdaten zu einem Zustand werden, der Zustand zu einer Entscheidung wird und die Entscheidung zu einer Anordnung wird, bevor sich das Fenster schließt. In einem solchen Umfeld werden elegante Meinungen, die einer Messung nicht standhalten können, schnell und meist ohne Vorwarnung ausgeraubt.

Deshalb ist die Frage nach C++ und Rust in HFT interessant. Nicht weil eine Sprache heilig und die andere betrügerisch ist, sondern weil HFT eine der seltenen Domänen ist, die das gesamte Argument dazu zwingt, sich im tatsächlichen Systemverhalten niederzuschlagen. Entweder behält der heiße Pfad unter Druck seine Form oder nicht. Die Tail-Latenz bleibt entweder diszipliniert oder nicht. Replay sagt entweder die Wahrheit oder nicht. Architektur ist dort kein Persönlichkeitstest. Es handelt sich um eine Rechnung.

Aus diesem Grund lautet die Antwort auch nicht „C++ für immer“ oder „Alles in Rust neu schreiben, denn Sicherheit ist gut und Angst ein Geschäftsmodell.“ Die ehrlichere Antwort ist enger gefasst und daher nützlicher. C++ dominiert immer noch die heißesten HFT-Pfade, da die umgebende Welt der Werkzeuge, Vorschubhandhabung, Speichersteuerung, Profilierung und hardwarenahen Praxis weiterhin extrem C++ geprägt ist. Rust ist rund um diesen Kern und manchmal auch innerhalb sorgfältig ausgewählter Teile davon wirklich nützlich, aber es löscht nicht die grundlegende Tatsache aus, dass der Handel mit geringer Latenz Abstraktionsfehler schneller bestraft, als die meisten Teams die Initiative umbenennen können.

Beim richtigen Gespräch geht es also nicht um Identität. Es geht um Systemgrenzen. Welche Teile des Stapels benötigen eine strenge Kontrolle über Speicher, Layout, Warteschlangen, Affinität und Verbindungsverhalten? Welche Teile profitieren am meisten von strengeren Korrektheitsbeschränkungen und sichereren Standardwerten? Welche Teile verdienen eine hybride Behandlung statt Stammesreinheit? Diese Fragen sind weitaus weniger glamourös als Sprachpredigten, aber es sind auch die Fragen, die den Kontakt mit der Produktion überleben.

Warum HFT schlechte technische Philosophie teuer aussehen lässt

HFT ist ungewöhnlich gut darin, eine bekannte technische Lüge aufzudecken: die Lüge, dass durchschnittliches Verhalten ausreicht. In vielen gewöhnlichen Produkten kann ein System respektabel bleiben, während gelegentliches Chaos hinter Durchsatz, Wiederholungsversuchen oder Benutzergeduld verborgen bleibt. Bei HFT ist die durchschnittliche Latenz interessant, aber das Schwanzverhalten ist oft der Teil, der Sie tatsächlich demütigt. Ein System, das schnell aussieht, bis es zur falschen Zeit zuckt, ist kein schnelles System im wirtschaftlich sinnvollen Sinne. Es handelt sich um einen Vertrauenstrick mit angehängtem Benchmark.

Aus diesem Grund reagieren HFT-Ingenieure allergisch auf ungenaue Abstraktionen. Sie erfahren, dass eine zusätzliche Zuteilung auf dem heißen Pfad nicht „nur eine Zuteilung“ ist. Dies ist eine mögliche Quelle für Jitter. Ein Warteschlangensprung ist nicht „nur ein Warteschlangensprung“. Es ist ein weiterer Ort, an dem Zeit gespeichert wird, die Koordination erweitert wird und die Sicht schlechter wird. Eine Cache-feindliche Struktur ist nicht nur ein ästhetischer Makel. Es handelt sich um eine fortlaufende Steuer auf jedes Marktereignis, das das System durchläuft. Multiplizieren Sie das mit dem tatsächlichen Feed-Volumen, und plötzlich wird eine Designauswahl aus einem Slide-Deck zu einem wiederkehrenden Posten im Budget für Enttäuschungen.

Rust mischt sich mit legitimer Kraft in dieses Gespräch ein, weil Speichersicherheit, Parallelitätskorrektheit wichtig sind und Systemcode bessere Standardeinstellungen verdient als „Bitte seien Sie vorsichtig, während Sie mit Messern über einer Grube jonglieren.“ Dieser Teil ist wahr. Aber HFT belohnt die Wahrheit nicht isoliert. Es belohnt die vereinte Wahrheit. Sicherheit ist wichtig, ja. Das gilt auch für ausgereifte Feed-Handler, stabile ABI-Grenzen, Replay-Tools, profilgesteuerte Iteration, ausgereifte Exchange-Integrationskultur und die Fähigkeit, genau zu prüfen, was die Maschine tut, wenn der Markt unfreundlich ist. C++ kommt in den meisten HFT-Umgebungen immer noch mit mehr der umgebenden Infrastruktur an.

Dies ist einer der Gründe, warum Einkäufer und technische Führungskräfte Reinheitsnarrativen widerstehen sollten. Eine Sprache kann in einer engen Dimension hervorragend sein und dennoch die falsche Standardeinstellung für den zeitkritischsten Teil eines Stapels sein, wenn das umgebende Ökosystem, die Tools und die Erfahrung des Teams den tatsächlichen Bereitstellungspfad nicht unterstützen. HFT ist der Ort, an dem schöne lokale Wahrheiten erfahren, dass der gesamte Weg noch wichtiger ist.

Der Stapel ist keine Sache, daher sollte die Sprachwahl nicht etwas anderes vortäuschen

Einer der dümmsten Fehler bei ernsthafter Systemarbeit besteht darin, über „den HFT-Stack zu sprechen, als wäre er ein einzelner technischer Organismus mit einer bevorzugten Sprache. Das ist es nicht. Es handelt sich um eine Ansammlung von Pfaden mit sehr unterschiedlichen Belastungen und Ausfallkosten.

Der Marktdatenerfassungspfad hat ein Temperament. Der Pfad zur Auftragsbuchaktualisierung hat einen anderen. Die Strategielogik kann numerisch dicht, aber strukturell eng sein. Risikoprüfungen sind oft latenzempfindlich, aber auf langweilige, erwachsene und rechtlich folgenreiche Weise auch korrektheitsempfindlich. Simulations- und Wiedergabeinfrastruktur bevorzugen möglicherweise Determinismus und Selbstbeobachtung gegenüber reiner Eitelkeit im Nanosekundenbereich. Steuerungsebenentools, Bereitstellungshilfen und Bedieneroberflächen legen viel mehr Wert auf Zuverlässigkeit, Wartbarkeit und Integrationshygiene als darauf, fünf Mikrosekunden bei einem Pfad einzusparen, den kein Kunde jemals sehen wird.

Das ist wichtig, weil hier oft ein vernünftiges Gespräch zwischen C++ und Rust beginnt. C++ bleibt am stärksten, wenn der Weg brutal heiß, hardwarebewusst, integrationsintensiv und bereits von jahrelanger nativer Betriebspraxis umgeben ist. Rust wird attraktiver, wenn der Weg immer noch wichtig ist, aber der wirtschaftliche Wert stärkerer Ausfälle, klarerer Eigentumsverhältnisse und einer geringeren Speicherrisikoexposition die Kosten der Ökosystemreibung überwiegt.

In der Praxis führt dies häufig zu hybriden Ergebnissen. Die beliebtesten Feed-Handling- und Gateway-Pfade bleiben in C++. Replay-Tools, Konfigurationsvalidierung, bestimmte risikoseitige Helfer, Dienstprogramme zur Nachrichtennormalisierung, Audit-Tools oder interne Komponenten für den Bediener können ausgezeichnete Rust-Kandidaten sein. Das ist keine Unentschlossenheit. Es ist architektonisches Erwachsensein. Das System wird als eine Reihe realer Grenzen und nicht als Sprachfandom mit einem Rechenzentrum behandelt.

Wo C++ immer noch die heißesten Wege besitzt

C++ behält seinen Platz in HFT aus Gründen, die weniger mystisch sind, als Außenstehende manchmal glauben. Der erste Grund ist die Speicher- und Layoutkontrolle. HFT Hot Paths kümmern sich darum, welche Daten zusammenleben, wie sich Strukturen im Cache verhalten, wie Eigentum unter Last angezeigt wird und ob das System allokationsdiszipliniert bleiben kann, wenn der Markt aufhört, höflich zu sein. C++ gibt Ingenieuren immer noch einen ungewöhnlich direkten Einfluss auf diese Entscheidungen, und das in einem Ökosystem, das bereits Jahrzehnte damit verbracht hat, herauszufinden, welche „kleinen“ Kosten insgeheim groß sind.

Der zweite Grund ist die Werkzeugdichte. C++ in HFT bedeutet nicht nur eine Sprache. Es bedeutet Compiler, Sanitizer, Flammendiagramme, C++, VTune, Replay-Harnesses, Austauschadapter, Warteschlangen-Folklore, Allokator-Know-how und eine riesige Menge an Leistungskriegsgeschichten, die unter finanziellem Druck angesammelt wurden. Dort fangen die Teams nicht bei Null an. Sie erben eine tiefe Betriebskultur, und diese Kultur ist wichtig, weil HFT maßvolle Iteration weitaus mehr belohnt als rhetorische Sauberkeit.

Der dritte Grund ist die Integrationsschwerkraft. Austausch, native Netzwerkpfade, Paketerfassungstools, Kernel-nahe Optimierung, FPGA-nahe Infrastruktur und das gesamte Ökosystem mit geringer Latenz sind in einer C- und C++-Welt immer noch sehr komfortabel. Rust kann mit dieser Welt interagieren, und zwar manchmal sehr effektiv, aber „kann interagieren mit“ ist nicht dasselbe wie „ist der Weg der geringsten Reibung durch das gesamte System.“ Im ernsthaften HFT ist Reibung keine emotionale Unannehmlichkeit. Es handelt sich gleichzeitig um eine mögliche Latenzsteuer, eine Debugging-Steuer und eine Liefersteuer.

Es gibt auch einen subtileren Grund, der im Zeitalter von AI wichtiger ist: C++ verfügt einfach über mehr Betriebsspeicher für diese Arbeit. AI-Codierungssysteme, Codesuche, öffentliche Beispiele, Anbieter-Snippets, Optimierer-Folklore und Debugging-Trails sind in Systemen mit geringer Latenz um C++ dichter als um Rust. Das macht C++ nicht edler. Es erleichtert Menschen und AI-Tools die Zusammenarbeit innerhalb hässlicher echter Codebasen, deren Charme schon vor Jahren erloschen ist.

Wo Rust tatsächlich hilft, anstatt Moral zu vertreten

Rust hilft am meisten, wenn es ein echtes Problem löst, anstatt als Persönlichkeitszubehör für Architekturdiagramme zu fungieren. In HFT erscheinen die stärksten Rust-Anwendungsfälle oft rund um den heißen Kern und nicht im absoluten Zentrum.

Rust ist nützlich für Komponenten, bei denen Korrektheitsfehler teuer sind, das Latenzbudget jedoch nicht mit einem Mikroskop gemessen wird. Nachrichtenvalidierungsebenen, Konfigurations- und Bereitstellungstools, bestimmte Protokollnormalisierungspfade, Kontrolldienste, Verwaltungsprogramme, Offline-Analysatoren und interne Operator-Tools können von der Ausrichtung der Sprache auf Explizitheit profitieren. Dabei geht es nicht darum, modern auszusehen. Es geht darum, die Klasse der dummen, sich wiederholenden und strukturell vermeidbaren Fehler zu reduzieren, die die Aufmerksamkeit von wichtigerer Arbeit ablenken.

Rust kann auch bei sorgfältig ausgewählten nahezu heißen Komponenten helfen, wenn das Team über das richtige Fachwissen verfügt und die Grenzen ehrlich sind. Ein Parser mit geringer Latenz, eine Bounded-State-Machine oder ein Stück deterministische Infrastruktur könnten ein solider Rust-Kandidat sein, wenn das Team die FFI- und Allokationsgeschichte unter Kontrolle halten kann und wenn die umgebende Ökosystembelastung im Voraus verstanden wird und nicht um 2:40 Uhr morgens während eines Rollouts entdeckt wird, den niemand wollte.

Aber genau hier brauchen Teams Disziplin. Rust ist nicht wertvoll, wenn es als glaubensbasierte Erneuerung in die Mitte eines nativen Handelsstapels gelegt wird. Es ist wertvoll, wenn die Grenze sauber ist, der Messpfad offensichtlich ist und die Betriebskosten der Integration geringer sind als der dadurch erzielte Sicherheits- oder Wartbarkeitsgewinn. Andernfalls wird das Projekt zu einer schönen Fallstudie darüber, wie man viel Zeit in die Entwicklung investieren kann, um Unsicherheiten beiseite zu schieben.

Die Grenze ist wichtiger als die Predigt

Ein häufiger Fehler bei Diskussionen zwischen C++ und Rust ist die Annahme, dass die Verwendung von Rust automatisch Gefahren beseitigt. Das ist nicht der Fall. Es ändert sich, wo die Gefahr liegt. In HFT ist diese Grenzfrage besonders wichtig, da heiße Pfade selten an der Sprachgrenze enden. Sie enden an Netzwerkgrenzen, Warteschlangengrenzen, Planungsgrenzen, FFI-Grenzen und Datenlayoutgrenzen.

Wenn eine Rust-Komponente in einen C++-Austauschadapter wechseln, mit einer nativen Warteschlange kommunizieren, Daten an eine Strategie-Engine mit strengen Layoutannahmen übergeben oder deterministisches Verhalten über Grenzübergänge hinweg beibehalten muss, dann besteht die eigentliche technische Arbeit nicht darin, dass „wir Rust verwendet haben.“ Die eigentliche Arbeit besteht darin, wie sorgfältig die Naht definiert und überprüft wurde. Unsicheres Verhalten kann immer noch durch Nichtübereinstimmung von ABI, Eigentumsverwirrung, versteckte Kopien, Warteschlangenfehler oder zeitliche Überraschungen entstehen. Die Sprache allein ist nicht Ihr Governance-Modell. Die Grenze ist.

Aus diesem Grund sprechen reife Teams von einem schmalen, heißen Weg und einer schmalen, unsicheren Oberfläche. Sie verlassen sich nicht auf Slogans wie „Speichersicherheit standardmäßig“, um ein grundsätzliches Systemdesignproblem zu lösen. Gute Teams stellen hässlichere und daher nützlichere Fragen. Wo erfolgt die Kopie? Wo ist der Warteschlangensprung? Welcher Seite gehört der Puffer? Welcher Pfad weist zu? Was passiert beim Gegendruck? Was ist wiederholbar? Was kann isoliert bewertet werden, und was muss durchgängig bewertet werden, da lokale Erfolge lange Tradition darin haben, zu globalen Enttäuschungen zu werden?

Praktische Fälle, die es wert sind, zuerst gelöst zu werden

Das klügste erste Projekt besteht selten darin, „den heißen Weg neu zu schreiben“. Das ist das technische Äquivalent dazu, ein Haus zu betreten und zu entscheiden, dass die erste sinnvolle Maßnahme darin besteht, das gesamte Skelett auszutauschen, bevor man prüft, welches Rohr bereits die Küche überschwemmt.

Das bessere erste Projekt ist eines dieser:

Beweisarbeit für Futterverarbeiter

Wenn das Team darüber streitet, ob Parsing, Normalisierung, Warteschlange oder Übergabe wirklich das Latenzproblem sind, erstellen Sie zuerst den Beweispfad. Erfassen Sie repräsentativen Datenverkehr, geben Sie ihn deterministisch wieder und zwingen Sie das System zu erkennen, wo Zeit und Jitter tatsächlich in die Kette gelangen. Die meisten HFT-Systeme brauchen hier keine weitere Ideologie. Sie brauchen einen besseren Lügendetektor.

Bereinigung von Gateway- und Risikogrenzen

Viele Stacks werden durch die Kernstrategielogik nicht ruiniert. Sie werden durch die Unzulänglichkeit der Grenzen zwischen Risiko, Gateway-Logik und betrieblicher Koordination ruiniert. Eine sorgfältige Umschreibung oder Umstrukturierung dieser Nähte kann die Zuverlässigkeit und Diagnostizierbarkeit verbessern, ohne das kommerzielle Risiko einzugehen, zuerst die absolut heißeste Schleife zu berühren.

Bereinigung der Hybrid-Steuerebene

Wenn Bedienertools, Bereitstellungshilfen, Wiederherstellungsprogramme oder Wiedergabetools fragil sind, kann Rust dort ein starker Kandidat sein. Diese Komponenten prägen häufig die Gesundheit der gesamten Organisation, auch wenn sie nicht im schnellsten Mikrosekundenbereich liegen. Sauberere Tools können das heiße System beruhigen, ohne so zu tun, als ob jede Binärdatei im Bestand die gleiche Sprache verdient.

Praktisches Labor: Bauen Sie einen winzigen Sequenzlückendetektor und machen Sie ihn ehrlich

Lassen Sie uns das Labor klein und nützlich halten. HFT-Systeme leben und sterben durch Sequenzdisziplin, lange bevor sie eine glamouröse Strategielogik erreichen. Dieses Spielzeugprogramm gibt einen Feed-ähnlichen Stream wieder und meldet, wo Lücken entstanden sind.

main.cpp

#include <cstdint>
#include <iostream>
#include <string>
#include <vector>

struct Packet {
    std::uint64_t seq;
    std::string payload;
};

struct Gap {
    std::uint64_t expected;
    std::uint64_t received;
};

class GapDetector {
public:
    void on_packet(const Packet& packet) {
        if (!started_) {
            expected_ = packet.seq + 1;
            started_ = true;
            return;
        }

        if (packet.seq != expected_) {
            gaps_.push_back({expected_, packet.seq});
        }

        expected_ = packet.seq + 1;
    }

    const std::vector<Gap>& gaps() const {
        return gaps_;
    }

private:
    bool started_ = false;
    std::uint64_t expected_ = 0;
    std::vector<Gap> gaps_;
};

int main() {
    std::vector<Packet> replay{
        {1001, "AAPL bid"},
        {1002, "AAPL ask"},
        {1003, "MSFT bid"},
        {1007, "MSFT ask"},
        {1008, "NVDA bid"},
        {1011, "NVDA ask"}
    };

    GapDetector detector;
    for (const auto& packet : replay) {
        detector.on_packet(packet);
    }

    if (detector.gaps().empty()) {
        std::cout << "no gaps\n";
        return 0;
    }

    for (const auto& gap : detector.gaps()) {
        std::cout << "gap expected=" << gap.expected
                  << " received=" << gap.received << "\n";
    }
}

Bauen

Auf Linux oder macOS:

g++ -O2 -std=c++20 -o gap_detector main.cpp
./gap_detector

Auf Windows:

cl /O2 /std:c++20 main.cpp
.\main.exe

Warum diese kleine Übung wichtig ist

Weil es die richtige Art des Denkens erzwingt:

  • deterministische Zustandsaktualisierung
  • ehrliche Reihenfolge
  • Wiederholung vor der Theorie
  • begrenztes, messbares Verhalten

Das ist bereits mehr HFT als überraschend viele Konferenzfolien.

Testaufgaben für Enthusiasten

  1. Portieren Sie denselben Detektor auf Rust und vergleichen Sie nicht die Benchmark-Vanalität, sondern die Klarheit der Grenzen, die Abhängigkeitsreibung und wie einfach jede Version zu Ihren vorhandenen Werkzeugen passt.
  2. Erweitern Sie die Wiedergabe, damit fehlende Pakete später in der falschen Reihenfolge eintreffen können, und entscheiden Sie dann, ob der Detektor sie puffern, ablehnen oder markieren soll.
  3. Fügen Sie das Timing hinzu und messen Sie den Unterschied zwischen einer vektorgestützten Wiedergabe und einer ringpuffergestützten Wiedergabe.
  4. Führen Sie eine unnötige Zuordnung auf dem heißen Pfad ein und messen Sie, wie schnell eine „kleine“ Entscheidung das Ergebnis verunreinigt.
  5. Fügen Sie einen Protokollierungszweig in on_packet hinzu und beobachten Sie, wie schnell die Beobachtbarkeit zur Sabotage wird, wenn sie unvorsichtig platziert wird.

Zusammenfassung

Bei der eigentlichen Konversation zwischen C++ und Rust in HFT geht es nicht darum, welche Sprache die schönere Mythologie verdient. Es geht darum, welche Teile des Systems eine direkte Kontrolle benötigen, welche Teile von stärkeren Standardeinstellungen profitieren und welche Grenzen ehrlich genug gestaltet werden können, um Hybriddesign ohne Illusionen zu unterstützen.

C++ dominiert immer noch die heißesten HFT-Pfade, da die Domäne die Kontrolle über Speicherlayout, Warteschlangen, Verbindungsverhalten, Profilierung, Wiedergabe und Integration mit einem ausgereiften Ökosystem mit geringer Latenz belohnt. Rust ist dort nützlich, wo Korrektheit, Deutlichkeit und Wartbarkeit mehr Wert schaffen als zusätzliche Reibungskosten für das Ökosystem. Beide können zu einem ernsthaften Stack gehören. Der Schritt der Erwachsenen besteht darin, zu entscheiden, wo, und die Beweise und nicht das Sprachfandom punkten zu lassen.

Referenzen

  1. NASDAQ TotalView-ITCH-Spezifikation: ITCH
  2. FIX Standards der Handelsgemeinschaft: FIX
  3. DPDK-Dokumentation: https://doc.dpdk.org/guides/
  4. Linux Zeitstempeldokumentation: Linux
  5. Brendan Gregg über Flame Graphs: https://www.brendangregg.com/flamegraphs.html
  6. Das Rust Performance-Buch: Rust
Philip P.

Philip P. – CTO

Zurück zu Blogs

Kontakt

Gespräch starten

Ein paar klare Zeilen genügen. Beschreiben Sie das System, den Druck und die Entscheidung, die blockiert wird. Oder schreiben Sie direkt an midgard@stofu.io.

01 Was das System macht
02 Was jetzt weh tut
03 Welche Entscheidung ist blockiert
04 Optional: Protokolle, Spezifikationen, Spuren, Unterschiede
0 / 10000
Keine Datei ausgewählt