Systèmes & construction

La pile à faible latence

structurel
Revu le 4 juin 2026. En 2026 : un trait permanent du marché, pas un avantage qui s'érode.

Contournement du noyau, busy-polling, discipline de cache, épinglage NUMA : l'ingénierie qui grappille des microsecondes. Nécessaire pour les jeux de vitesse, démesuré pour la plupart des stratégies de microstructure.

L'idée

Le chemin chaud, et la queue qui fait mal schéma annotéDG-STACK
Le chemin chaud ▼ paquet de données entrant NIC + bypass0.5–1 µs Décodage0.1–0.5 µs Carnet0.1–0.5 µs Stratégie0.5–3 µs Risque0.1–0.5 µs Ordre sortant0.5–1 µs médiane ≈ 2–7 µs tick-to-trade file sans verrou chemin froid logs · alloc · tableaux de bord ne bloque jamais la ligne chaude Distribution de la latence médiane ~5 µs p99 voici la transaction que vous perdez la médiane est une métrique de vanité ; la queue est la stratégie

Ce que montre ce schéma. Le chemin chaud est l'unique ligne serrée du paquet entrant à l'ordre sortant ; tout ce qui est lent (logs, allocation) est poussé vers un chemin froid qui ne le bloque jamais, via une file sans verrou. Un logiciel discipliné atteint une médiane de quelques microsecondes, mais l'ingénierie vit vraiment dans la queue. L'événement p99, en marché rapide, est celui que vous perdez, alors vous optimisez la queue, pas la moyenne.

Qu'est-ce que le « chemin critique », et pourquoi tient-on tout le reste à l'écart ?

Le chemin critique est la séquence de code essentielle qui s'exécute à chaque événement de données de marché susceptible de mener à une transaction : décoder le paquet, mettre à jour le carnet, évaluer le signal, vérifier le risque, envoyer l'ordre. Tout ce qui n'en fait pas partie (journalisation, allocation mémoire, entrées-sorties fichier, administration, supervision) est rejeté sur un chemin froid hors du fil critique, parce que tout ce que le chemin critique attend devient une part de votre latence.

L'intuition est celle d'un sprinteur de relais qui doit passer le témoin à l'instant où il arrive. Tout ce qui le fait s'arrêter (nouer un lacet, regarder son téléphone) coûte la course. Le chemin critique est le sprinteur ; le chemin froid est tout ce que vous faites entre les courses, agencé pour ne jamais en interrompre une.

Le chemin critique est donc à usage unique et dépouillé : pas d'allocations (la mémoire est préallouée en pools), pas d'appels système, pas de verrous, pas de journalisation qui touche le disque. Chaque morceau d'état dont il a besoin est déjà en mémoire et idéalement déjà en cache. Le chemin froid gère tout le reste sur des fils et cœurs séparés (écrire les journaux, persister les exécutions, mettre à jour les tableaux de bord, toute forme d'intendance) et il communique avec le chemin critique via une file sans verrou pour que le chemin critique ne bloque jamais. Cette discipline est le principe organisateur de toute l'architecture du système : le gestionnaire de flux, le constructeur de carnet, la stratégie et la passerelle d'ordres sont agencés pour que le travail critique en latence soit une ligne serrée et prévisible et que tout le reste soit asynchrone.

Pourquoi l'architecture événementielle est-elle la bonne forme ?

Le trading est intrinsèquement réactif : vous ne faites rien tant que le marché ne vous envoie pas un événement, un changement de cotation, une transaction, un minuteur. Une conception événementielle modélise exactement cela : une boucle qui attend le prochain événement et le dispatche à travers le chemin critique. Elle évite la surcharge du sondage permanent, conserve un ordre de traitement unique et déterministe, et se calque proprement sur le flux entrant.

Le cœur est une boucle d'événements : récupérer le prochain événement (un message de données de marché, un accusé de réception d'ordre, un tic d'horloge) le dispatcher à son gestionnaire, recommencer. Un fil, un ordonnancement, aucune surprise sur ce qui a tourné quand. Ce déterminisme est aussi ce qui rend le système assez reproductible pour backtester fidèlement : le simulateur rejoue le même flux d'événements à travers la même boucle, si bien que production et backtest partagent le code de stratégie et l'ordre de traitement.

Les événements viennent de la couche de messagerie (le flux multicast livre les mises à jour du carnet, la session d'ordres livre les rapports d'exécution, des minuteurs internes se déclenchent) et chacun est un petit événement typé que la boucle route. C'est l'opposé d'un service web requête/réponse : il n'y a pas d'« attente d'une base de données », le système réagit à un flux. La seule contrainte de conception qui en découle est ne jamais bloquer la boucle, ce qui est exactement pourquoi tout ce qui est lent vit sur le chemin froid.

Quelle est la différence entre faible latence et faible gigue, et pourquoi la queue compte-t-elle davantage ?

La latence est le temps que met un événement à être traité ; la gigue est la mesure dans laquelle ce temps varie. Un système à 5 µs de médiane mais avec un pic occasionnel à 500 µs perdra précisément quand cela compte le plus, durant les rafales volatiles que vous voulez le plus trader. Les ingénieurs HFT s'obsèdent donc sur la queue (p99, p99,9, max), pas sur la moyenne. La prévisibilité est le produit.

L'intuition est un train à l'heure en moyenne mais aléatoirement en retard de quarante minutes : inutile pour attraper une correspondance, parce qu'on planifie autour du pire qu'on rencontrera plausiblement, pas autour de la moyenne. En trading, le pire cas se déclenche précisément durant les marchés rapides où l'opportunité (et la sélection adverse) est la plus grande. La médiane est une métrique de vanité ; la queue est la stratégie.

Un budget de latence se construit contre un percentile de queue, pas contre la moyenne. Le nombre qui décide si vous gagnez la transaction disputée est la latence à haut percentile durant une rafale, qui peut valoir plusieurs fois la médiane si la gigue n'est pas maîtrisée.
Lp99=inf{:P(latency)0.99},Lp99Lmedian    jitter problemL_{p99} = \inf\{\,\ell : \mathbb{P}(\text{latency} \le \ell) \ge 0.99\,\}, \qquad L_{p99} \gg L_{\text{median}} \;\Rightarrow\; \text{jitter problem}

La gigue vient de nombreuses petites sources, chacune un pic imprévisible : ordonnancement de l'OS (votre fil préempté), interruptions, ramasse-miettes (une raison d'éviter les langages gérés sur le chemin critique), défauts de cache, défauts de page, accès NUMA inter-socket, contention sur un verrou. On la combat structurellement : épingler le fil du chemin critique sur un cœur isolé dédié sans ordonnanceur ni autre travail, busy-poller au lieu de dormir, préallouer et préfaulter la mémoire, désactiver la mise à l'échelle de fréquence d'économie d'énergie, garder les données locales à un seul nœud NUMA, et éviter tout chemin de code qui peut occasionnellement faire quelque chose de coûteux. Un budget de latence se construit contre la queue, pas la moyenne, parce que la queue est la transaction que vous perdez.

Qu'est-ce que le contournement du noyau, et pourquoi DPDK / Solarflare ?

La pile réseau du noyau de l'OS ajoute des microsecondes et de la gigue à chaque paquet : copies, interruptions, changements de contexte. Le contournement du noyau permet à votre application de lire les paquets directement depuis la carte réseau en espace utilisateur, en sautant le noyau. DPDK (un cadriciel logiciel) et Solarflare/Onload (pilote plus NIC) sont les moyens standard d'y parvenir, tous deux réduisant et stabilisant la latence entrante.

L'intuition : le chemin normal est comme le courrier passant par un centre de tri, où chaque lettre est manipulée, mise en file et remise avec surcharge. Le contournement du noyau est une glissière de livraison directe du facteur à votre bureau : moins de mains, un timing bien plus prévisible. DPDK mappe les anneaux de paquets du NIC dans votre processus pour que vous les sondiez directement, sans noyau, sans interruption par paquet et sans copie ; Solarflare avec Onload accélère de façon transparente les sockets standard sur la même idée, et les NIC FPGA vont encore plus loin. Tout cela rogne quelques microsecondes et, plus important, retire une grosse source de gigue du flux multicast entrant.

Le coût est réel : le contournement du noyau implique en général de busy-poller un cœur 100 % du temps (il ne dort jamais à attendre une interruption) si bien qu'il brûle du CPU et de l'énergie contre de la latence. C'est un arbitrage délibéré. En 2026, c'est un outillage mûr et banalisé : posséder un NIC à contournement du noyau est un achat, l'une des raisons pour lesquelles l'infrastructure est le prix d'entrée, pas l'avantage.

Que sont le busy-polling, les files sans verrou et la convivialité cache ?

Ce sont les trois techniques centrales du chemin critique. Le busy-polling signifie que le fil boucle à vérifier s'il y a du travail au lieu de dormir, échangeant du CPU contre une latence de réveil nulle. Les files sans verrou font passer des données entre fils sans mutex, si bien qu'aucun fil ne bloque jamais. La convivialité cache dispose les données pour que le CPU les trouve dans le cache rapide L1/L2 plutôt que de caler sur la mémoire principale.

Busy-polling (attente active) : un fil endormi met des microsecondes à se réveiller quand un événement arrive, une gigue inacceptable sur le chemin critique. À la place le fil tourne en rond, sondant continuellement l'anneau du NIC et la file d'entrée, si bien qu'il réagit à l'instant où la donnée apparaît. Le prix est un cœur pleinement occupé et avide d'énergie : ça vaut le coup sur le chemin critique, nulle part ailleurs. Les files sans verrou (typiquement un tampon circulaire mono-producteur/mono-consommateur) laissent le chemin critique remettre du travail au chemin froid, et recevoir des accusés d'ordre, sans jamais prendre de verrou. Un verrou peut bloquer le chemin critique pendant qu'un autre fil le détient ; les conceptions sans verrou garantissent que le fil critique progresse toujours, supprimant une source majeure de gigue.

La convivialité cache compte parce que la hiérarchie mémoire est brutalement inégale : un succès L1 coûte environ une nanoseconde, tandis qu'un aller-retour vers la mémoire principale coûte à peu près une centaine. On garde donc les données chaudes petites et contiguës (des tableaux, pas des structures chaînées à chasse de pointeurs), on évite le faux partage (deux cœurs se battant sur une même ligne de cache), et on dispose le carnet pour qu'une recherche touche le moins de lignes de cache possible. À l'échelle de budgets en microsecondes, les défauts de cache sont un coût dominant.

L'écart de latence entre cache et mémoire principale est d'environ deux ordres de grandeur, si bien qu'à un budget de quelques microsecondes une poignée de défauts de cache évitables peut faire la différence entre gagner et perdre l'événement.
tL11ns    tDRAM100nstDRAMtL1100t_{\text{L1}} \approx 1\,\text{ns} \;\lll\; t_{\text{DRAM}} \approx 100\,\text{ns} \quad\Rightarrow\quad \frac{t_{\text{DRAM}}}{t_{\text{L1}}} \approx 100

Ces trois techniques, plus l'épinglage de cœur et la préallocation, sont ce qui fait passer un logiciel compétent de « dizaines de microsecondes » à « quelques microsecondes », le palier logiciel à contournement du noyau sous la frontière du silicium.

C++ ou Rust : le choix du langage compte-t-il ?

Pour le chemin critique vous voulez un langage compilé, sans ramasse-miettes avec un contrôle manuel sur la mémoire et la disposition, ce qui en 2026 signifie C++ (l'incumbent, avec le plus de bibliothèques faible latence et de support de chaîne d'outils FPGA) ou Rust (sûr en mémoire avec le même contrôle, et une part croissante). Les langages gérés comme Java et C# apparaissent hors du chemin critique ou dans les paliers tolérants, où leurs pauses de GC en latence de queue sont acceptables.

Le non-négociable est pas de ramasse-miettes sur le chemin critique. Une pause de GC est exactement le genre de pic imprévisible de plusieurs millisecondes qui détruit la latence de queue. C++ et Rust ont une gestion mémoire déterministe, manuelle ou à portée, si bien qu'il n'y a pas de pause à subir. C++ reste le défaut : des décennies d'idiomes faible latence, l'écosystème de bibliothèques le plus profond, et les chaînes d'outils pour le co-design FPGA le ciblent toutes. Rust offre le même contrôle à coût nul avec une sûreté mémoire à la compilation, ce qui élimine une classe de bugs catastrophiques dans un système qui peut perdre de l'argent en microsecondes, et son adoption monte.

La place de Python est réelle mais bornée : c'est le langage de la recherche (backtests, exploration de signaux, le pipeline de la recherche à la production) pas le chemin critique en direct. Une forme courante est « recherche en Python, chemin critique de production en C++/Rust », et réconcilier les deux est sa propre discipline. Ce que l'IA change : les assistants LLM sont désormais réellement utiles pour écrire et optimiser ce code : traduire un prototype de recherche en un chemin critique C++/Rust, suggérer des dispositions conviviales au cache, repérer une allocation sur le chemin critique. Le jugement sur quoi optimiser et comment le valider reste humain (voir ce que l'IA change pour le HFT). L'IA assiste l'optimisation ; elle ne change pas la physique.

Comment mesure-t-on vraiment la latence ?

Vous horodatez le même événement à plusieurs points (le paquet au NIC, décodage fait, l'ordre sur le fil) et enregistrez la distribution, pas la moyenne. L'étalon-or est l'horodatage matériel/NIC et la capture sur le fil externe (un tap passif qui horodate les paquets indépendamment), parce que les horloges logicielles perturbent ce qu'elles mesurent. Vous rapportez la médiane, p99, p99,9 et max.

Pourquoi des distributions et pas des moyennes : comme ci-dessus, la queue est ce qui vous coûte, et un unique nombre moyen cache exactement les pics qui vous importent. Rapportez les percentiles et le max, et tracez l'histogramme. Pour savoir passe le temps, prenez des horodatages internes aux étapes du chemin critique (par exemple via le compteur rdtsc du CPU) qui vous donnent la ventilation étape par étape ; pour la vérité de terrain tick-to-trade, un horodatage matériel externe de NIC ou de switch, ou un tap passif sur le fil, mesure la boucle sans que votre propre instrumentation n'ajoute latence ou gigue.

Le piège est une mesure qui ment. Journaliser chaque événement sur le chemin critique, ou appeler une horloge coûteuse, change la chose que vous mesurez, donc vous échantillonnez, utilisez des compteurs bon marché, et agrégez hors du chemin critique. Cette ventilation est précisément l'entrée du budget de latence : une fois que vous savez où vont les microsecondes, vous décidez quelle étape vaut la peine d'être attaquée (contournement du noyau, FPGA, ou colocation) et laquelle est déjà assez petite pour être ignorée.

Exemple travaillé

Prenez une ventilation tick-to-trade schématique pour un système logiciel à contournement du noyau en colocation, en 2026 (plages illustratives, pas des promesses ; mesurez les vôtres, le propos est la forme). Le tick-to-trade est additif, donc le budget n'est que la somme des étapes de « paquet de données de marché entrant » à « ordre sortant ».

Le tick-to-trade est la somme des latences d'étape. Avec un logiciel à contournement du noyau discipliné en colocation, la médiane atterrit dans les quelques microsecondes ; le p99 est là où vit vraiment l'ingénierie.
Ttick-to-trade=tNIC+tdecode+tbook+tstrategy+trisk+tgatewayT_{\text{tick-to-trade}} = t_{\text{NIC}} + t_{\text{decode}} + t_{\text{book}} + t_{\text{strategy}} + t_{\text{risk}} + t_{\text{gateway}}

Étape par étape, les contributions typiques et le levier qui attaque chacune : NIC + contournement du noyau (paquet vers l'espace utilisateur) environ 0,5–1 µs, le levier étant DPDK ou Solarflare ; décodage du protocole binaire environ 0,1–0,5 µs, le levier étant une disposition à offsets fixes sans parsing ; mise à jour du carnet environ 0,1–0,5 µs, le levier une structure de carnet conviviale au cache ; stratégie/signal environ 0,5–3 µs, le levier un code de chemin critique serré sur un état précalculé ; contrôle de risque pré-négociation environ 0,1–0,5 µs, en ligne sans entrées-sorties ; et encodage + envoi de l'ordre environ 0,5–1 µs, le levier un protocole d'ordre binaire sur contournement du noyau. Le total médian tick-to-trade atterrit à environ 2–7 µs.

La leçon que les nombres enseignent est dans l'écart entre la médiane et la queue. Cette médiane est atteignable avec un logiciel discipliné, mais le p99 est là où vit vraiment l'ingénierie : non maîtrisé, l'événement au 99e percentile pourrait valoir 30 µs parce qu'un fil a été préempté ou qu'une ligne de cache était froide, et c'est cet événement, durant un marché rapide, que vous perdez. L'isolation de cœur, le busy-polling et l'absence de GC sont ce qui maintient la queue proche de la médiane. Un chemin FPGA effondre la portion décodage-plus-stratégie-plus-encodage dans les dizaines à centaines de nanosecondes fil-à-fil, le seul moyen de passer le plancher logiciel, à un coût. Comparez le besoin réel de votre stratégie à cela avant de dépenser un centime sur la vitesse ; la plupart des stratégies vivent bien au-dessus de ce plancher, où le logiciel à faible gigue est décisif et bien moins cher que le silicium. Les chiffres sont illustratifs et datés de 2026 ; ceci est pédagogique seulement, pas un conseil en investissement.

Où cela s'inscrit