Baja latencia Unix / Linux


11

La mayoría de los trabajos de programación de baja latencia / alta frecuencia (basados ​​en especificaciones de trabajo) parecen implementarse en plataformas Unix. En muchas de las especificaciones hacen una solicitud particular para las personas con el tipo de experiencia "Linux de baja latencia".

Suponiendo que esto no significa un sistema operativo Linux en tiempo real, ¿podría la gente ayudarme con lo que esto podría referirse? Sé que puedes configurar la afinidad de la CPU a los subprocesos, pero supongo que están pidiendo mucho más que eso.

Kernel tuning? (aunque escuché que fabricantes como solarflare producen tarjetas de red de derivación del núcleo de todos modos)?

¿Qué pasa con DMA o posiblemente memoria compartida entre procesos? Si la gente me pudiera dar ideas breves, puedo ir y hacer la investigación en Google.

(Esta pregunta probablemente requerirá a alguien familiarizado con el comercio de alta frecuencia)


2
El ajuste del kernel es el camino a seguir para que un sistema operativo en tiempo no real sea lo más real posible. La fijación de hilos también es obligatoria. Puede leer más sobre eso en este artículo: coralblocks.com/index.php/2014/04/…
rdalmeida

Respuestas:


26

He realizado una buena cantidad de trabajo apoyando a grupos HFT en entornos de IB y Hedge Fund. Voy a responder desde la vista sysadmin, pero algo de esto también es aplicable a la programación en tales entornos.

Hay un par de cosas que un empleador suele buscar cuando se refieren al soporte de "baja latencia". Algunas de estas son preguntas de "velocidad bruta" (¿sabe qué tipo de tarjeta de 10 g comprar y en qué ranura colocarla?), Pero más de ellas se refieren a las formas en que un entorno de Comercio de alta frecuencia difiere de un tradicional Entorno Unix. Algunos ejemplos:

  • Unix se ajusta tradicionalmente para admitir la ejecución de una gran cantidad de procesos sin privar a ninguno de ellos de recursos, pero en un entorno HFT, es probable que desee ejecutar una aplicación con un mínimo absoluto de sobrecarga para el cambio de contexto, y así sucesivamente. Como un pequeño ejemplo clásico, activar hyperthreading en una CPU Intel permite que se ejecuten más procesos a la vez, pero tiene un impacto significativo en el rendimiento de la velocidad a la que se ejecuta cada proceso individual. Como programador, también tendrá que mirar el costo de las abstracciones como el enhebrado y RPC, y descubrir dónde una solución más monolítica, aunque menos limpia, evitará gastos generales.

  • TCP / IP generalmente se ajusta para evitar caídas de conexión y hacer un uso eficiente del ancho de banda disponible. Si su objetivo es obtener la latencia más baja posible de un enlace muy rápido, en lugar de obtener el mayor ancho de banda posible de un enlace más restringido, querrá ajustar el ajuste de la pila de red. Desde el punto de vista de la programación, también querrá ver las opciones de socket disponibles y descubrir cuáles tienen los valores predeterminados más ajustados para el ancho de banda y la confiabilidad que para reducir la latencia.

  • Al igual que con las redes, con el almacenamiento: querrá saber cómo distinguir un problema de rendimiento de almacenamiento de un problema de aplicación y aprender qué patrones de uso de E / S tienen menos probabilidades de interferir con el rendimiento de su programa (como ejemplo, aprenda dónde la complejidad del uso de IO asíncrono puede ser rentable para usted y cuáles son las desventajas).

  • Finalmente, y más dolorosamente: los administradores de Unix queremos tanta información sobre el estado de los entornos que monitoreamos como sea posible, por lo que nos gusta ejecutar herramientas como agentes SNMP, herramientas de monitoreo activo como Nagios y herramientas de recopilación de datos como sar (1). Sin embargo, en un entorno donde los cambios de contexto deben ser minimizados y el uso del disco y la red IO estrictamente controlados, tenemos que encontrar la compensación correcta entre el gasto de monitoreo y el rendimiento básico de las cajas monitoreadas. Del mismo modo, ¿qué técnicas está utilizando que facilitan la codificación pero le están costando el rendimiento?

Finalmente, hay otras cosas que simplemente vienen con el tiempo; trucos y detalles que aprendes con experiencia. Pero estos son más especializados (¿cuándo uso epoll? ¿Por qué dos modelos de servidores HP con controladores PCIe teóricamente idénticos funcionan de manera tan diferente?), Más vinculados a lo que esté usando su tienda específica y más probabilidades de cambiar de un año a otro .


1
Gracias, aunque estaba interesado en una respuesta de programación, esto fue muy útil e informativo.
user997112

55
@ user997112 Esta es una respuesta de programación. Si no parece así, sigue leyéndolo hasta que lo haga :)
Tim Post

15

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


"matrices de bits múltiples en lugar de propiedades de matriz de objetos con bool" ¿qué quiere decir con esto?
user997112

1
He elaborado con ejemplos y enlaces.
JBRWilkinson

Yendo un paso más allá, en lugar de usar un byte completo para indicar si un pedido se ha completado o no, puede usar un solo bit. Entonces, en una sola línea de caché (64 bytes), podría representar el estado de 256 pedidos. Entonces, menos fallas.
Quixver

Además, si está haciendo escaneos lineales de memoria, el prefetcher de hardware hace un gran trabajo al cargar sus datos. Siempre que acceda a la memoria de forma secuencial o a paso o algo simple. Pero si está accediendo a la memoria de cualquier manera no secuencial, el prefetcher de la CPU se confunde. Por ejemplo, una búsqueda binaria. En ese punto, el programador puede ayudar a la CPU con sugerencias: _mm_prefetch.
Quixver

-2

Como no puse uno o dos programas de alta frecuencia en producción, diría las cosas más importantes:

  1. La configuración del hardware y los administradores del sistema junto con los ingenieros de redes NO definen un buen resultado del número de pedidos procesados ​​por el sistema de negociación, pero pueden degradarlo en gran medida si no conocen los conceptos básicos descritos anteriormente.
  2. La única persona que realmente hace que el sistema realice operaciones de alta frecuencia es un informático que reúne el código en c ++

    Entre los conocimientos utilizados se encuentra

    A. Comparar e intercambiar operaciones.

    • cómo se usa CAS en el procesador y cómo lo admite la computadora para usarse en el llamado procesamiento de estructura sin bloqueo. O procesamiento sin bloqueo. No voy a escribir un libro completo aquí. En resumen, el compilador GNU y el compilador Microsoft admiten el uso directo de instrucciones CAS. Permite que su código tenga "No.Wair" mientras extrae el elemento de la cola o coloca uno nuevo en la cola.
  3. El científico talentoso usará más. Debería encontrar en los nuevos "patrones" recientes uno que apareció primero en Java. Llamado patrón DISRUPTOR. Pliegue en el intercambio de LMAX en Europa explicó a la comunidad de alta frecuencia que la utilización basada en subprocesos en los procesadores modernos perdería el tiempo de procesamiento en la liberación de la memoria caché por parte de la CPU si la cola daya no está alineada con el tamaño de la caché de la CPU moderna = 64

    Entonces, para esa lectura, hicieron público un código java que permite que el proceso de subprocesos múltiples use el caché de la CPU del hardware correctamente sin resoluciones de conflicto. Y un buen científico de la computación TIENE que encontrar que ese patrón ya fue portado a c ++ o portarse a sí mismo.

    Esta es una forma de competencia más allá de cualquier configuración de administrador. Esto está en el corazón real de alta frecuencia hoy.

  4. El experto en informática TIENE que escribir una gran cantidad de código C ++ no solo para ayudar a las personas de control de calidad. Pero también
    • validar en los comerciantes se enfrentan a la velocidad comprobada lograda
    • condenar las diferentes tecnologías usadas y exponerlas con su propio código para demostrar que no producen buenos resultados
    • escriba su propio código c ++ de comunicación de subprocesos múltiples basado en la velocidad probada de kernel pupe / select en lugar de usar nuevamente tecnologías antiguas. Le daré un ejemplo: la biblioteca moderna de TCP es ICE. Y la gente que lo hizo es brillante. Pero sus prioridades estaban en el campo de la compatibilidad con muchos idiomas. Entonces. Puedes hacerlo mejor en c ++. Por lo tanto, busque ejemplos de rendimiento más alto en función de la llamada selectiva ASINCRÓNICA. Y no apueste por múltiples consumidores, múltiples productores, no por HF.
      Y se sorprenderá al descubrir que la tubería se usa SOLO PARA la notificación del núcleo del mensaje recibido. Puede poner el número de mensaje de 64 bits allí, pero para el contenido, vaya a la cola CAS sin bloqueo. Activado por una select()llamada asíncrona del núcleo .
    • además. Obtenga información acerca de cómo asignar con afinidad de hilo de c ++ a su hilo que canaliza / pone en cola sus mensajes. Ese hilo debe tener afinidad central. Nadie más debería usar el mismo número de núcleo de CPU.
    • y así.

Como puede ver, la alta frecuencia es un CAMPO EN DESARROLLO. No puede ser solo un programador de C ++ para tener éxito.

Y cuando digo tener éxito, quiero decir que el fondo de cobertura para el que trabajaría reconocerá los esfuerzos de la gira en compensación anual más allá del número de personas y reclutadores que hablan.

Los tiempos de las preguntas frecuentes de constructor / destructor simples se han ido para siempre. Y c ++ ... migró con nuevos compiladores para liberarlo de la administración de memoria y para forzar la no herencia de gran profundidad en las clases. Pérdida de tiempo. El paradigma de reutilización de código cambió. No se trata solo de cuántas clases hiciste en polimorfo. Se trata del rendimiento del código directamente confirmado en el tiempo que puede reutilizar.

Entonces, es su elección entrar en la curva de aprendizaje allí, o no. Nunca llegará a una señal de stop.


66
Es posible que desee poner un poco de esfuerzo en la ortografía y el formato. En su forma actual, esta publicación es apenas comprensible.
CodesInChaos

1
Describes la situación de hace 10 años. Las soluciones basadas en hardware superan fácilmente a C ++ puro hoy en día, sin importar cuán optimizado esté su C ++.
Sjoerd

Para aquellos que quieran saber qué son las soluciones basadas en hardware, en su mayoría son soluciones FPGA donde el código se graba en la memoria rápida y no se cambia sin volver a generar la llamada memoria ROM. Solo lectura
alex p

@alexp Claramente no sabes de lo que estás hablando. FPGA es algo diferente a "código grabado en memoria rápida".
Sjoerd
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.