C++, Rust, och högfrekvent handel: där deterministisk latens avgör argumentet

C++, Rust, och högfrekvent handel: där deterministisk latens avgör argumentet

C++, Rust, och högfrekvent handel: där deterministisk latens avgör argumentet

Introduktion

Debatter på programmeringsspråk tolereras vanligtvis eftersom de flesta system har råd med lite teater. En tjänst är lite ineffektiv, en kö blir bredare än den borde, en återförsökspolicy gör något moraliskt tveksamt, och alla fortsätter att röra sig eftersom produkten fortfarande fungerar, intäkterna landar fortfarande och latensdiagrammet är fult på ett överlevbart sätt.

Högfrekvent handel är mindre sentimental. Det bryr sig inte om vilket språk som vann internet det här kvartalet. Den bryr sig om huruvida marknadsdata blir statlig, stat blir ett beslut och beslutet blir en order innan fönstret stängs. I den typen av miljö blir eleganta åsikter som inte kan överleva mätning rånade snabbt och oftast utan förvarning.

Det är därför frågan om C++ och Rust i HFT är intressant. Inte för att det ena språket är heligt och det andra är bedrägligt, utan för att HFT är en av de sällsynta domänerna som tvingar hela argumentet att betala ut i faktiska systembeteende. Den heta banan håller antingen sin form under tryck eller så gör den inte det. Svanslatensen förblir antingen disciplinerad eller så gör den det inte. Replay säger antingen sanningen eller så gör den inte det. Arkitektur är inget personlighetstest där. Det är en faktura.

Det är också därför som svaret inte är "C++ för alltid" eller "skriv om allt i Rust eftersom säkerhet är bra och rädsla är en affärsmodell." Det mer ärliga svaret är smalare och därför mer användbart. C++ dominerar fortfarande de hetaste HFT-banorna eftersom den omgivande världen av verktyg, foderhantering, minneskontroll, profilering och hårdvaruangränsande praxis förblir extremt C++-formad. Rust är verkligen användbar kring den kärnan, och ibland inuti noggrant utvalda delar av den, men det raderar inte det grundläggande faktumet att handel med låg latens straffar abstraktionsmisstag snabbare än de flesta lag kan byta namn på initiativet.

Så rätt konversation handlar inte om identitet. Det handlar om systemgränser. Vilka delar av stacken behöver brutal kontroll över minne, layout, köer, affinitet och trådbeteende? Vilka delar drar mest nytta av starkare korrekthetsbegränsningar och säkrare standardinställningar? Vilka delar förtjänar hybridbehandling istället för stamrenhet? De frågorna är mycket mindre glamorösa än språkpredikningar, men det är också de frågor som överlever kontakten med produktionen.

Varför HFT får dålig teknisk filosofi att se dyr ut

HFT är ovanligt bra på att avslöja en välbekant ingenjörslögn: lögnen om att det räcker med ett genomsnittligt beteende. I många vanliga produkter kan ett system förbli respektabelt samtidigt som det döljer enstaka kaos bakom genomströmning, omförsök eller användarens tålamod. I HFT är genomsnittlig latens intressant, men svansbeteende är ofta den del som faktiskt förödmjukar dig. Ett system som ser snabbt ut tills det rycker vid fel tidpunkt är inte ett snabbt system i någon kommersiell mening. Det är ett självförtroendetrick med ett riktmärke bifogat.

Det är därför HFT ingenjörer blir allergiska mot oprecisa abstraktioner. De lär sig att en extra tilldelning på den heta vägen inte är "bara en tilldelning". Det är en möjlig källa till jitter. Ett köhopp är inte "bara ett köhopp". Det är en annan plats där tiden lagras, koordinationen utökas och synligheten blir sämre. En cache-fientlig struktur är inte bara en estetisk skavank. Det är en pågående skatt på varje marknadshändelse som passerar systemet. Multiplicera det med verklig matningsvolym och plötsligt blir ett designval från ett slide deck en återkommande post i budgeten för besvikelse.

Rust går in i den här konversationen med legitim kraft eftersom minnessäkerhet spelar roll, samtidighet korrekt och systemkod förtjänar bättre standardinställningar än "var försiktig när du jonglerar med knivar över en grop." Den delen är sann. Men HFT belönar inte sanningen isolerat. Det belönar kombinerad sanning. Säkerheten är viktig, ja. Det gör även mogna foderhanterare, stabila ABI gränser, replayverktyg, profildriven iteration, mogen utbytesintegrationskultur och förmågan att inspektera exakt vad maskinen gör när marknaden är ovänlig. C++ kommer fortfarande med mer av den omgivande infrastrukturen i de flesta HFT miljöer.

Detta är en anledning till att köpare och ingenjörsledare bör motstå renhetsberättelser. Ett språk kan vara utmärkt i en smal dimension och fortfarande vara fel standard för den mest tidskänsliga delen av en stack om det omgivande ekosystemet, verktygen och teamupplevelsen inte stöder den faktiska leveransvägen. HFT är dit underbara lokala sanningar går för att lära sig att hela vägen fortfarande betyder mer.

Stacken är inte en sak, så språkvalet ska inte låtsas annat

Ett av de dummaste misstagen i seriöst systemarbete är att tala om "HFT-stacken" som om det vore en enda teknisk organism med ett föredraget språk. Det är det inte. Det är en samling vägar med väldigt olika tryck och felkostnader.

Banan för intag av marknadsdata har ett temperament. Sökvägen för uppdatering av orderboken har en annan. Strategilogik kan vara numeriskt tät men strukturellt smal. Riskkontroller är ofta latenskänsliga men också korrekthetskänsliga på ett tråkigt, vuxet, juridiskt konsekvent sätt. Simulering och replay-infrastruktur kan prisa determinism och introspektion över rå nanosekundsfåfänga. Verktyg på styrplan, utbyggnadshjälpare och operatörsytor bryr sig mycket mer om tillförlitlighet, underhållsbarhet och integrationshygien än de bryr sig om att raka fem mikrosekunder från en väg som ingen kund någonsin kommer att se.

Detta är viktigt eftersom det ofta är där en förnuftig C++ och Rust konversation börjar. C++ förblir starkast när vägen är brutalt het, hårdvarumedveten, integrationstung och redan omgiven av år av inhemsk operativ praktik. Rust blir mer attraktiv när vägen fortfarande är viktig men det ekonomiska värdet av starkare standarder, tydligare ägande och snävare exponering för minnesrisk uppväger kostnaden för ekosystemfriktion.

I praktiken leder det ofta till hybridresultat. De hetaste foderhanterings- och gatewayvägarna stannar i C++. Uppspelningsverktyg, konfigurationsvalidering, vissa hjälpmedel på risksidan, verktyg för meddelandenormalisering, granskningsverktyg eller interna operatörsvända komponenter kan vara utmärkta Rust-kandidater. Detta är inte obeslutsamhet. Det är arkitektonisk vuxen ålder. Systemet behandlas som en uppsättning verkliga gränser snarare än som ett språkfandom med ett datacenter.

Där C++ fortfarande äger de hetaste vägarna

C++ behåller sin plats i HFT av skäl som är mindre mystiska än vad utomstående ibland föreställer sig. Det första skälet är minnes- och layoutkontroll. HFT heta vägar bryr sig om vilka data som lever ihop, hur strukturer beter sig i cache, hur ägande uppstår under belastning och om systemet kan förbli allokeringsdisciplinerat när marknaden slutar vara artig. C++ ger fortfarande ingenjörer ovanligt direkt inflytande över dessa val, och det gör det i ett ekosystem som redan har ägnat decennier åt att lära sig vilka "små" kostnader som i hemlighet är stora.

Det andra skälet är verktygstätheten. C++ i HFT betyder inte bara ett språk. Det betyder kompilatorer, desinficeringsmedel, låggrafer, C++, VTune, replay-selar, utbytesadaptrar, köande folklore, allokeringsexpertis och en stor mängd prestationskrigshistorier samlade under ekonomisk press. Lag börjar inte från noll där. De ärver en djup operativ kultur, och den kulturen är viktig eftersom HFT belönar uppmätt iteration långt mer än retorisk renlighet.

Det tredje skälet är integrationens gravitation. Utbyten, inbyggda nätverksvägar, verktyg för paketfångst, optimering av kärnan, FPGA-angränsande infrastruktur och hela ekosystemet med låg latens är fortfarande mycket bekväma i en C- och C++-värld. Rust kan interagera med den världen, och ibland mycket effektivt, men "kan interagera med" är inte samma sak som "är vägen för minsta friktion genom hela systemet." I allvarlig HFT är friktion inte en känslomässig olägenhet. Det är en möjlig latensskatt, en felsökningsskatt och en leveransskatt på samma gång.

Det finns också en mer subtil anledning som är viktigare i AI-eran: C++ har helt enkelt mer operativt minne tillgängligt kring detta arbete. AI kodsystem, kodsökning, offentliga exempel, leverantörsfragment, optimizerfolklore och felsökningsspår är tätare runt C++ i system med låg latens än runt Rust. Det gör inte C++ ädlare. Det gör det enklare för människor och AI verktyg att samarbeta i fula riktiga kodbaser vars charm gick ut för flera år sedan.

Där Rust faktiskt hjälper istället för att utföra moral

Rust hjälper det mesta när det löser ett verkligt problem snarare än att fungera som ett personlighetstillbehör för arkitekturdiagram. I HFT dyker ofta de starkaste Rust användningsfallen upp runt den heta kärnan snarare än i den absoluta mitten av den.

Rust är användbart för komponenter där korrekthetsfel är dyra men latensbudgeten inte mäts med ett mikroskop. Meddelandevalideringslager, konfigurations- och distributionsverktyg, vissa protokollnormaliseringsvägar, kontrolltjänster, administrativa verktyg, offlineanalysatorer och interna operatörers verktyg kan dra nytta av språkets partiskhet mot tydlighet. Poängen där är att inte se modern ut. Poängen är att minska klassen av dumma, repetitiva, strukturellt undvikande misstag som drar uppmärksamheten från viktigare arbete.

Rust kan också hjälpa till med noggrant utvalda nästan heta komponenter när teamet har rätt expertis och gränsen är ärlig. En parser med låg latens, en avgränsad tillståndsmaskin eller en bit av deterministisk infrastruktur kan vara en solid Rust-kandidat om teamet kan hålla FFI och allokeringsberättelsen under kontroll och om den omgivande ekosystemets börda förstås i förväg snarare än att den upptäcks klockan 2:40 på morgonen under en utrullning.

Men det är precis där lag behöver disciplin. Rust är inte värdefullt när det släpps i mitten av en infödd handelsstack som en trosbaserad renovering. Det är värdefullt när gränsen är ren, mätvägen är uppenbar och driftskostnaden för integrationen är lägre än säkerhets- eller underhållsvinsten den skapar. Annars blir projektet en vacker fallstudie i hur man kan lägga seriös ingenjörstid på att flytta osäkerhet i sidled.

Gränsen betyder mer än predikan

Ett vanligt misstag i diskussioner om C++ kontra Rust är att anta att användning av Rust automatiskt tar bort fara. Det gör det inte. Det förändras var faran sitter. I HFT är den gränsfrågan särskilt viktig eftersom heta banor sällan slutar vid språklinjen. De slutar vid nätverksgränser, kögränser, schemaläggningsgränser, FFI gränser och datalayoutgränser.

Om en Rust-komponent måste passera in i en C++-växlingsadapter, prata med en inbyggd kö, lämna data till en strategimotor med snäva layoutantaganden eller upprätthålla deterministiskt beteende över gränsövergångar, så är det verkliga ingenjörsarbetet inte "vi använde Rust." Det verkliga arbetet är hur noggrant sömmen definierades och verifierades. Osäkert beteende kan fortfarande uppstå genom ABI oöverensstämmelse, ägarförvirring, dolda kopior, kömisstag eller tidsöverraskningar. Språket ensamt är inte din styrningsmodell. Gränsen är.

Det är därför mogna team talar om en smal het stig och en smal osäker yta. De förlitar sig inte på slogans som "minnessäkerhet som standard" för att lösa det som i grunden är ett systemdesignproblem. Bra team ställer fulare och därför mer användbara frågor. Var sker kopian? Var är köhoppet? Vilken sida äger bufferten? Vilken väg allokerar? Vad händer vid mottryck? Vad är omspelbart? Vad kan jämföras isolerat, och vad måste jämföras från slut till slut eftersom lokala vinster har en lång tradition av att bli globala besvikelser?

Praktiska fall värda att lösa först

Det smartaste första projektet är sällan "skriva om den heta vägen." Det är den tekniska motsvarigheten till att gå in i ett hus och bestämma den första användbara handlingen är att byta ut hela skelettet innan man kontrollerar vilket rör som redan svämmar över köket.

Det bättre första projektet är ett av dessa:

Bevisarbete för foderhanterare

Om teamet bråkar om huruvida analys, normalisering, köbildning eller handoff verkligen är latensproblemet, bygg bevisvägen först. Fånga representativ trafik, spela om den deterministiskt och tvinga systemet att erkänna var tid och jitter faktiskt kommer in i kedjan. De flesta HFT system behöver inte mer ideologi här. De behöver en bättre lögndetektor.

Rengöring av gateway och riskgräns

Många stackar förstörs inte av kärnstrategins logik. De är förstörda av gränsen slarv mellan risk, gateway-logik och operativ koordination. En noggrann omskrivning eller omstrukturering i dessa sömmar kan förbättra tillförlitligheten och diagnoserbarheten utan den kommersiella risken att röra den absolut hetaste slingan först.

Hybrid rensning av kontrollplan

Om operatörsverktyg, distributionshjälpare, återställningsverktyg eller replay-verktyg är ömtåliga kan Rust vara en stark kandidat där. Dessa komponenter formar ofta hela organisationens hälsa även när de inte sitter i den snabbaste mikrosekundvägen. Renare verktyg kan göra det heta systemet lugnare utan att låtsas att varje binär i dödsboet förtjänar samma språk.

Praktiskt labb: Bygg en liten sekvensavståndsdetektor och gör den ärlig

Låt oss hålla labbet litet och användbart. HFT system lever och dör av sekvensdisciplin långt innan de når glamorös strategilogik. Detta leksaksprogram spelar upp en feedliknande ström och rapporterar var luckor uppstod.

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";
    }
}

Bygga

På Linux eller macOS:

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

På Windows:

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

Varför denna lilla övning är viktig

Eftersom det tvingar fram rätt sorts tänkande:

  • deterministisk tillståndsuppdatering
  • ärlig sekvensering
  • repris före teorin
  • begränsat, mätbart beteende

Det är redan mer HFT än ett överraskande antal konferensbilder.

Testuppgifter för entusiaster

  1. Porta samma detektor till Rust och jämför inte riktmärke för fåfänga, utan gränstydlighet, beroendefriktion och hur lätt varje version passar dina befintliga verktyg.
  2. Förläng uppspelningen så att saknade paket senare kan komma ur funktion, bestäm sedan om detektorn ska buffra, avvisa eller flagga dem.
  3. Lägg till timing och mät skillnaden mellan en vektorstödd repris och en ringbuffertstödd repris.
  4. Inför en onödig allokering på den heta vägen och mät hur snabbt ett "litet" beslut börjar förorena resultatet.
  5. Lägg till en loggningsgren inuti on_packet och se hur snabbt observerbarhet blir sabotage när den placeras slarvigt.

Sammanfattning

Det verkliga C++- och Rust-samtalet i HFT handlar inte om vilket språk som förtjänar den trevligare mytologin. Det handlar om vilka delar av systemet som behöver direkt kontroll, vilka delar som drar nytta av starkare standardinställningar och vilka gränser som kan göras ärliga nog att stödja hybriddesign utan vanföreställningar.

C++ dominerar fortfarande de hetaste HFT-vägarna eftersom domänen belönar kontroll över minneslayout, köbildning, trådbeteende, profilering, omspelning och integration med ett moget ekosystem med låg latens. Rust är användbart där korrekthet, tydlighet och underhållsbarhet skapar mer värde än ytterligare ekosystemfriktionskostnader. Båda kan höra hemma i en seriös stack. Det vuxna draget är att bestämma var och att låta bevis snarare än språkfandom hålla poäng.

Referenser

  1. NASDAQ TotalView-ITCH specifikation: ITCH
  2. FIX Handelsgemenskapsstandarder: FIX
  3. DPDK-dokumentation: https://doc.dpdk.org/guides/
  4. Linux tidsstämplingsdokumentation: Linux
  5. Brendan Gregg om Flame Graphs: https://www.brendangregg.com/flamegraphs.html
  6. Rust Performance Book: Rust
Philip P.

Philip P. – CTO

Tillbaka till bloggar

Kontakta

Starta konversationen

Några tydliga streck räcker. Beskriv systemet, trycket och beslutet som blockeras. Eller skriv direkt till midgard@stofu.io.

01 Vad systemet gör
02 Vad gör ont nu
03 Vilket beslut är blockerat
04 Valfritt: loggar, specifikationer, spår, diff
0 / 10000
Ingen fil har valts