C++, Rust y el comercio de alta frecuencia: donde la latencia determinista decide el argumento
Introducción
Los debates sobre lenguajes de programación suelen ser tolerados porque la mayoría de los sistemas pueden permitirse un poco de teatro. Un servicio es un poco ineficiente, una cola se vuelve más ancha de lo que debería, una política de reintento hace algo moralmente cuestionable y todos siguen moviéndose porque el producto aún funciona, los ingresos aún llegan y el gráfico de latencia es feo en términos de supervivencia.
El comercio de alta frecuencia es menos sentimental. No importa qué idioma ganó Internet este trimestre. Le importa si los datos del mercado se convierten en estado, el estado en una decisión y la decisión en una orden antes de que se cierre la ventana. En ese tipo de ambiente, las opiniones elegantes que no pueden sobrevivir a la medición son asaltadas rápidamente y generalmente sin previo aviso.
Por eso es interesante la cuestión de C++ y Rust en HFT. No porque un lenguaje sea sagrado y el otro fraudulento, sino porque HFT es uno de los raros dominios que obliga a todo el argumento a cobrar provecho en el comportamiento real del sistema. El camino caliente mantiene su forma bajo presión o no. La latencia de cola permanece disciplinada o no. La repetición dice la verdad o no. Allí la arquitectura no es una prueba de personalidad. Es una factura.
Esta es también la razón por la que la respuesta no es "C++ para siempre" o "reescribir todo en Rust porque la seguridad es buena y el miedo es un modelo de negocio". La respuesta más honesta es más limitada y, por tanto, más útil. C++ todavía domina los caminos más populares HFT porque el mundo circundante de herramientas, manejo de alimentación, control de memoria, creación de perfiles y prácticas adyacentes al hardware sigue siendo extremadamente C++. Rust es realmente útil en torno a ese núcleo y, a veces, dentro de partes cuidadosamente elegidas del mismo, pero no borra el hecho básico de que el comercio de baja latencia castiga los errores de abstracción más rápido de lo que la mayoría de los equipos pueden cambiar el nombre de la iniciativa.
Entonces, la conversación correcta no es sobre identidad. Se trata de límites del sistema. ¿Qué partes de la pila necesitan un control brutal sobre la memoria, el diseño, las colas, la afinidad y el comportamiento de los cables? ¿Qué partes se benefician más de restricciones de corrección más estrictas y valores predeterminados más seguros? ¿Qué partes merecen un tratamiento híbrido en lugar de pureza tribal? Esas preguntas son mucho menos glamorosas que los sermones lingüísticos, pero también son las preguntas que sobreviven al contacto con la producción.
Por qué HFT hace que la mala filosofía técnica parezca costosa
HFT es inusualmente bueno a la hora de exponer una conocida mentira de la ingeniería: la mentira de que el comportamiento promedio es suficiente. En muchos productos comunes, un sistema puede seguir siendo respetable y al mismo tiempo esconder un caos ocasional detrás del rendimiento, los reintentos o la paciencia del usuario. En HFT, la latencia promedio es interesante, pero el comportamiento de la cola suele ser la parte que realmente te humilla. Un sistema que parece rápido hasta que se mueve en el momento equivocado no es un sistema rápido en ningún sentido comercialmente significativo. Es un truco de confianza con un punto de referencia adjunto.
Por eso los ingenieros de HFT se vuelven alérgicos a las abstracciones imprecisas. Aprenden que una asignación adicional en el camino caliente no es "sólo una asignación". Es una posible fuente de inquietud. Un salto de cola no es "sólo un salto de cola". Es otro lugar donde se almacena el tiempo, se expande la coordinación y la visibilidad empeora. Una estructura hostil al caché no es sólo un defecto estético. Es un impuesto continuo sobre cada evento de mercado que pasa por el sistema. Multiplique eso por el volumen de alimentación real y, de repente, una elección de diseño de una serie de diapositivas se convierte en una partida recurrente en el presupuesto para la decepción.
Rust entra en esta conversación con fuerza legítima porque la seguridad de la memoria es importante, la corrección de la concurrencia es importante y el código del sistema merece mejores valores predeterminados que "tenga cuidado al hacer malabarismos con cuchillos sobre un hoyo". Esa parte es cierta. Pero HFT no recompensa la verdad de forma aislada. Premia la verdad combinada. La seguridad importa, sí. Lo mismo ocurre con los manipuladores de feeds maduros, los límites ABI estables, las herramientas de reproducción, la iteración basada en perfiles, la cultura madura de integración de intercambios y la capacidad de inspeccionar exactamente qué está haciendo la máquina cuando el mercado no es amable. C++ todavía llega con más infraestructura circundante en la mayoría de los entornos HFT.
Ésta es una de las razones por las que los compradores y los líderes de ingeniería deberían resistirse a las narrativas de pureza. Un lenguaje puede ser excelente en una dimensión limitada y aún así ser el predeterminado incorrecto para la parte más sensible al tiempo de una pila si el ecosistema circundante, las herramientas y la experiencia del equipo no respaldan la ruta de entrega real. HFT es adonde acuden las hermosas verdades locales para aprender que todo el camino aún importa más.
La pila no es una sola cosa, por lo que la elección del idioma no debe pretender lo contrario
Uno de los errores más tontos en el trabajo serio de sistemas es hablar de "la pila HFT" como si fuera un único organismo técnico con un lenguaje preferido. No lo es. Es una colección de caminos con presiones y costos de falla muy diferentes.
El camino de la ingesta de datos de mercado tiene un temperamento. La ruta de actualización del libro de pedidos tiene otra. La lógica estratégica puede ser numéricamente densa pero estructuralmente estrecha. Las comprobaciones de riesgos suelen ser sensibles a la latencia, pero también a la corrección, de una manera aburrida, adulta y con consecuencias legales. La infraestructura de simulación y reproducción puede valorar el determinismo y la introspección por encima de la pura vanidad de nanosegundos. Las herramientas del plano de control, los ayudantes de implementación y las superficies del operador se preocupan por la confiabilidad, la mantenibilidad y la higiene de la integración mucho más de lo que se preocupan por recortar cinco microsegundos de un camino que ningún cliente verá jamás.
Esto es importante porque a menudo es donde comienza una conversación sensata entre C++ y Rust. C++ sigue siendo más fuerte cuando el camino es brutalmente candente, consciente del hardware, con mucha integración y ya rodeado de años de práctica operativa nativa. Rust se vuelve más atractivo cuando el camino sigue siendo importante, pero el valor económico de impagos más fuertes, propiedad más clara y exposición más estrecha al riesgo de memoria supera el costo de la fricción del ecosistema.
En la práctica, esto a menudo conduce a resultados híbridos. Las rutas de acceso y manejo de feeds más populares permanecen en C++. Las herramientas de reproducción, la validación de configuraciones, ciertas ayudas de riesgo, las utilidades de normalización de mensajes, las herramientas de auditoría o los componentes internos orientados al operador pueden ser excelentes candidatos para Rust. Esto no es indecisión. Es la edad adulta arquitectónica. El sistema está siendo tratado como un conjunto de límites reales en lugar de un lenguaje fandom con un centro de datos.
Donde C++ todavía posee los caminos más populares
C++ mantiene su lugar en HFT por razones que son menos místicas de lo que los forasteros a veces imaginan. La primera razón es el control de la memoria y el diseño. HFT Las rutas activas se preocupan por qué datos conviven, cómo se comportan las estructuras en el caché, cómo se muestra la propiedad bajo carga y si el sistema puede permanecer disciplinado en la asignación cuando el mercado deja de ser educado. C++ todavía brinda a los ingenieros una influencia inusualmente directa sobre esas opciones, y lo hace dentro de un ecosistema que ya ha pasado décadas aprendiendo qué costos "pequeños" son secretamente grandes.
La segunda razón es la densidad de herramientas. C++ en HFT no significa solo un idioma. Significa compiladores, desinfectantes, gráficos de llama, C++, VTune, arneses de reproducción, adaptadores de intercambio, folklore de colas, experiencia en asignadores y un vasto conjunto de historias de guerra de desempeño acumuladas bajo presión financiera. Allí los equipos no empiezan de cero. Heredan una cultura operativa profunda, y esa cultura es importante porque HFT recompensa la iteración medida mucho más que la limpieza retórica.
La tercera razón es la gravedad de la integración. Los intercambios, las rutas de red nativas, las herramientas de captura de paquetes, la optimización adyacente al kernel, la infraestructura adyacente a FPGA y todo el ecosistema de baja latencia siguen siendo muy cómodos en un mundo C y C++. Rust puede interactuar con ese mundo, y a veces de manera muy efectiva, pero "puede interactuar con" no es lo mismo que "es el camino de menor fricción a través de todo el sistema". En HFT serio, la fricción no es un inconveniente emocional. Es un posible impuesto de latencia, un impuesto de depuración y un impuesto de entrega al mismo tiempo.
También hay una razón más sutil que importa más en la era AI: C++ simplemente tiene más memoria operativa disponible para este trabajo. Los sistemas de codificación AI, la búsqueda de códigos, los ejemplos públicos, los fragmentos de proveedores, el folklore de optimizadores y las rutas de depuración son más densos en C++ en sistemas de baja latencia que en Rust. Eso no hace que C++ sea más noble. Hace que sea más fácil para los humanos y las herramientas AI colaborar dentro de feas bases de código reales cuyo encanto expiró hace años.
Dónde Rust realmente ayuda en lugar de ejercer la moralidad
Rust ayuda más cuando resuelve un problema real en lugar de actuar como un accesorio de personalidad para diagramas de arquitectura. En HFT, los casos de uso más fuertes de Rust a menudo aparecen alrededor del núcleo activo en lugar de en el centro absoluto del mismo.
Rust es útil para componentes donde las fallas de corrección son costosas pero el presupuesto de latencia no se mide con un microscopio. Las capas de validación de mensajes, las herramientas de configuración e implementación, ciertas rutas de normalización de protocolos, los servicios de control, las utilidades administrativas, los analizadores fuera de línea y las herramientas de los operadores internos pueden beneficiarse del sesgo del lenguaje hacia la claridad. La cuestión es no parecer moderno. El objetivo es reducir la clase de errores tontos, repetitivos y estructuralmente evitables que desvían la atención de trabajos más importantes.
Rust también puede ayudar en componentes casi calientes cuidadosamente seleccionados cuando el equipo tiene la experiencia adecuada y los límites son honestos. Un analizador de baja latencia, una máquina de estado limitada o una pieza de infraestructura determinista pueden ser un candidato sólido para Rust si el equipo puede mantener bajo control la FFI y la historia de asignación y si la carga del ecosistema circundante se comprende de antemano en lugar de descubrirse a las 2:40 de la mañana durante un lanzamiento que nadie quería.
Pero aquí es exactamente donde los equipos necesitan disciplina. Rust no tiene valor cuando se coloca en medio de una pila comercial nativa como una renovación basada en la fe. Es valioso cuando el límite está claro, la ruta de medición es obvia y el costo operativo de la integración es menor que la ganancia de seguridad o mantenibilidad que crea. De lo contrario, el proyecto se convierte en un hermoso caso de estudio sobre cómo dedicar mucho tiempo de ingeniería a desviar la incertidumbre.
El límite importa más que el sermón
Un error común en las discusiones entre C++ y Rust es asumir que el uso de Rust elimina automáticamente el peligro. No es así. Cambia dónde se encuentra el peligro. En HFT, esa cuestión de límites es especialmente importante porque los caminos calientes rara vez terminan en la línea del idioma. Terminan en los límites de la red, los límites de las colas, los límites de la programación, los límites de FFI y los límites del diseño de datos.
Si un componente Rust debe cruzar a un adaptador de intercambio C++, hablar con una cola nativa, entregar datos a un motor de estrategia con supuestos de diseño estrictos o mantener un comportamiento determinista a través de transiciones de límites, entonces el verdadero trabajo de ingeniería no es "usamos Rust". El verdadero trabajo es el cuidado con el que se definió y verificó la costura. El comportamiento inseguro aún puede ocurrir a través de ABI falta de coincidencia, confusión de propiedad, copias ocultas, errores en las colas o sorpresas en el tiempo. El lenguaje por sí solo no es su modelo de gobernanza. El límite es.
Por eso los equipos maduros hablan de un camino estrecho y caliente y una superficie estrecha e insegura. No se basan en eslóganes como "seguridad de la memoria por defecto" para resolver lo que es fundamentalmente un problema de diseño del sistema. Los buenos equipos hacen preguntas más feas y, por tanto, más útiles. ¿Dónde ocurre la copia? ¿Dónde está el salto de cola? ¿A qué lado pertenece el buffer? ¿Qué camino asigna? ¿Qué sucede durante la contrapresión? ¿Qué es rejugable? ¿Qué se puede comparar de forma aislada y qué se debe comparar de extremo a extremo porque los triunfos locales tienen una larga tradición de convertirse en decepciones globales?
Casos prácticos que vale la pena resolver primero
El primer proyecto más inteligente rara vez es "reescribir el camino caliente". Esto es el equivalente técnico de entrar en una casa y decidir que el primer acto útil es sustituir todo el esqueleto antes de comprobar qué tubería está inundando ya la cocina.
El mejor primer proyecto es uno de estos:
Trabajo de evidencia de manipuladores de alimentos
Si el equipo discute sobre si el análisis, la normalización, la puesta en cola o la transferencia son realmente el problema de latencia, primero cree la ruta de evidencia. Capture el tráfico representativo, reprodúzcalo de manera determinista y obligue al sistema a confesar dónde entran realmente el tiempo y la inquietud en la cadena. La mayoría de los sistemas HFT no necesitan más ideología aquí. Necesitan un mejor detector de mentiras.
Limpieza de puertas de enlace y límites de riesgo
Muchas pilas no se arruinan con la lógica estratégica central. Están arruinados por la negligencia en los límites entre el riesgo, la lógica de entrada y la coordinación operativa. Una reescritura o reestructuración cuidadosa en esas uniones puede mejorar la confiabilidad y la capacidad de diagnóstico sin el riesgo comercial de tocar primero el circuito absolutamente más caliente.
Limpieza del plano de control híbrido
Si las herramientas del operador, los asistentes de implementación, las utilidades de recuperación o las herramientas de reproducción son frágiles, Rust puede ser un fuerte candidato allí. Estos componentes a menudo dan forma a la salud de toda la organización incluso cuando no se encuentran en el camino más rápido de microsegundos. Herramientas más limpias pueden hacer que el sistema caliente esté más tranquilo sin pretender que todos los binarios del estado merezcan el mismo lenguaje.
Laboratorio práctico: construya un pequeño detector de brechas de secuencia y hágalo honesto
Mantengamos el laboratorio pequeño y útil. Los sistemas HFT viven y mueren según la disciplina de secuencia mucho antes de alcanzar una lógica estratégica glamorosa. Este programa de juguetes reproduce una transmisión similar a una transmisión e informa dónde aparecieron las lagunas.
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";
}
}
Construir
En Linux o macOS:
g++ -O2 -std=c++20 -o gap_detector main.cpp
./gap_detector
En [[TECNOLOGÍA:Windows]]:
cl /O2 /std:c++20 main.cpp
.\main.exe
Por qué es importante este pequeño ejercicio
Porque obliga a pensar correctamente:
- actualización de estado determinista
- secuenciación honesta
- repetición antes de la teoría
- comportamiento limitado y medible
Esto ya es más HFT que un sorprendente número de diapositivas de conferencias.
Tareas de prueba para entusiastas
- Transfiera el mismo detector a Rust y compare no la vanidad de los puntos de referencia, sino la claridad de los límites, la fricción de dependencia y la facilidad con la que cada versión se adapta a sus herramientas existentes.
- Extienda la reproducción para que los paquetes faltantes puedan llegar más tarde desordenados y luego decida si el detector debe almacenarlos en buffer, rechazarlos o marcarlos.
- Agregue tiempo y mida la diferencia entre una repetición respaldada por vectores y una repetición respaldada por un búfer en anillo.
- Introduzca una asignación innecesaria en el camino caliente y mida qué tan rápido una "pequeña" decisión comienza a contaminar el resultado.
- Agregue una rama de registro dentro de
on_packety observe qué tan rápido la observabilidad se convierte en sabotaje cuando se coloca descuidadamente.
Resumen
La verdadera conversación entre C++ y Rust en HFT no se trata de qué idioma merece la mejor mitología. Se trata de qué partes del sistema necesitan control directo, qué partes se benefician de valores predeterminados más fuertes y qué límites pueden ser lo suficientemente honestos como para soportar el diseño híbrido sin engaños.
C++ todavía domina los caminos más populares HFT porque el dominio recompensa el control sobre el diseño de la memoria, las colas, el comportamiento de los cables, la creación de perfiles, la reproducción y la integración con un ecosistema maduro de baja latencia. Rust es útil cuando la corrección, la claridad y la mantenibilidad crean más valor que los costos adicionales de fricción del ecosistema. Ambos pueden pertenecer a una pila seria. La decisión de los adultos es decidir dónde y dejar que la evidencia, en lugar del lenguaje fanático, lleve la cuenta.
Referencias
- NASDAQ TotalView-ITCH especificación: ITCH
- FIX Estándares de la comunidad comercial: FIX
- Documentación DPDK: https://doc.dpdk.org/guides/
- Linux documentación de marca de tiempo: Linux
- Brendan Gregg sobre gráficos de llamas: https://www.brendangregg.com/flamegraphs.html
- El libro de rendimiento de Rust: Rust