Además de la excelente respuesta de ajuste de hardware / configuración de @jimwise, "Linux de baja latencia" implica:
- C ++ por razones de determinismo (sin demora sorpresiva mientras se activa GC), acceso a instalaciones de bajo nivel (E / S, señales), potencia del lenguaje (uso completo de TMP y STL, seguridad de tipo).
- preferir velocidad sobre memoria:> 512 Gb de RAM es común; las bases de datos son productos NoSQL en memoria, en caché por adelantado o exóticos.
- elección del algoritmo: tan rápido como sea posible versus sano / comprensible / extensible, por ejemplo, matrices de múltiples bits sin bloqueo en lugar de propiedades de matriz de objetos con bool.
- uso completo de las instalaciones del sistema operativo, como la memoria compartida, entre procesos en diferentes núcleos.
- seguro. El software HFT generalmente se ubica en una bolsa de valores, por lo que las posibilidades de malware son inaceptables.
Muchas de estas técnicas se superponen con el desarrollo de juegos, que es una de las razones por las cuales la industria del software financiero absorbe a los programadores de juegos recientemente redundantes (al menos hasta que pagan sus atrasos en el alquiler).
La necesidad subyacente es poder escuchar un flujo de datos de mercado de gran ancho de banda, como los precios de seguridad (acciones, productos básicos, fx) y luego tomar una decisión muy rápida de compra / venta / no hacer nada basada en la seguridad, el precio y tenencias actuales.
Por supuesto, todo esto también puede salir espectacularmente mal .
Así que explicaré el punto de las matrices de bits . Digamos que tenemos un sistema de comercio de alta frecuencia que opera en una larga lista de pedidos (Buy 5k IBM, Sell 10k DELL, etc.). Digamos que necesitamos determinar rápidamente si se han completado todos los pedidos, para que podamos pasar a la siguiente tarea. En la programación tradicional de OO, esto se verá así:
class Order {
bool _isFilled;
...
public:
inline bool isFilled() const { return _isFilled; }
};
std::vector<Order> orders;
bool needToFillMore = std::any_of(orders.begin(), orders.end(),
[](const Order & o) { return !o.isFilled(); } );
La complejidad algorítmica de este código será O (N) ya que es un escaneo lineal. Echemos un vistazo al perfil de rendimiento en términos de accesos a la memoria: cada iteración del bucle dentro de std :: any_of () va a llamar a o.isFilled (), que está en línea, por lo que se convierte en un acceso a la memoria de _isFilled, 1 byte (o 4 dependiendo de su arquitectura, compilador y configuración del compilador) en un objeto de digamos 128 bytes en total. Entonces estamos accediendo a 1 byte en cada 128 bytes. Cuando leemos el 1 byte, suponiendo el peor de los casos, obtendremos un error de caché de datos de CPU. Esto provocará una solicitud de lectura a RAM que lee una línea completa de RAM ( consulte aquí para obtener más información ) solo para leer 8 bits. Entonces el perfil de acceso a la memoria es proporcional a N.
Compare esto con:
const size_t ELEMS = MAX_ORDERS / sizeof (int);
unsigned int ordersFilled[ELEMS];
bool needToFillMore = std::any_of(ordersFilled, &ordersFilled[ELEMS+1],
[](int packedFilledOrders) { return !(packedOrders == 0xFFFFFFFF); }
El perfil de acceso a la memoria de este, suponiendo el peor de los casos nuevamente, es ELEMS dividido por el ancho de una línea RAM (varía, podría ser de doble canal o triple canal, etc.).
Entonces, en efecto, estamos optimizando algoritmos para patrones de acceso a memoria. Ninguna cantidad de RAM ayudará: es el tamaño del caché de datos de la CPU lo que causa esta necesidad.
¿Esto ayuda?
Hay un excelente CPPC sobre todo sobre la programación de baja latencia (para HFT) en YouTube: https://www.youtube.com/watch?v=NH1Tta7purM