Java no solo es una empresa sangrienta, sino también aplicaciones rápidas sensibles a la latencia

Hago comercio algorítmico en Raiffeisenbank. Esta es un área bastante específica del sector bancario. Creamos una plataforma de negociación que funciona con retrasos bajos y predecibles. El éxito de la aplicación depende, entre otras cosas, de la velocidad de la aplicación, por lo que tenemos que lidiar con toda la pila involucrada en el comercio: canales de red privados, hardware especial, configuración del sistema operativo y una JVM especial, y, por supuesto, la aplicación misma. No podemos dejar de optimizar exclusivamente la aplicación en sí misma: el sistema operativo o la configuración de red no son menos importantes. Esto requiere experiencia técnica y erudición para comprender cómo fluyen los datos a través de toda la pila y dónde puede haber un retraso.


No todas las organizaciones / bancos pueden permitirse el desarrollo de esta clase de software. Pero tuve la suerte de que tal proyecto se lanzó dentro de los muros de Raiffeisenbank, y tenía una especialización adecuada: me especialicé en el rendimiento del código en el Laboratorio Intel Moscú Compiler. Creamos compiladores para C, C ++ y Fortran. En Raiffeisenbank, me cambié a Java. Si antes hice algún tipo de herramienta que mucha gente usó entonces, ahora me he movido al otro lado de las barricadas y estoy involucrado en el análisis aplicado del rendimiento no solo del código, sino de toda la pila de aplicaciones. Regularmente, la forma de investigar un problema de rendimiento va mucho más allá del código, por ejemplo, en la configuración del núcleo o de la red.

Java no es para alta carga?


Se cree ampliamente que Java no es muy adecuado para el desarrollo de sistemas altamente cargados.
Esto solo se puede acordar parcialmente. Java es hermoso en muchos aspectos. Si lo comparamos con un lenguaje como C ++, entonces su sobrecarga potencial puede ser mayor, pero a veces soluciones funcionalmente similares en C ++ pueden funcionar más lentamente. Hay optimizaciones que funcionan automáticamente en Java, pero no funcionan en C ++, y viceversa. En cuanto a la calidad del código que viene después del compilador JIT de Java, quiero creer que el rendimiento será inferior a lo que podría lograr en el pico, no más de varias veces. Pero al mismo tiempo obtengo un desarrollo muy rápido, excelentes herramientas y una amplia selección de componentes listos para usar.

Seamos realistas: en el mundo C ++, los entornos de desarrollo (IDE) están significativamente detrás de IntelliJ y Eclipse. Si un desarrollador utiliza cualquiera de estos entornos, la velocidad de depuración, la búsqueda de errores y la escritura de lógica compleja es un orden de magnitud mayor. Como resultado, resulta que es más fácil archivar Java en los lugares correctos para que funcione lo suficientemente rápido como para hacer todo desde cero y durante mucho tiempo en C ++. Lo más divertido es que, al escribir código competitivo, los enfoques de sincronización tanto en Java como en C ++ son muy similares: son primitivos a nivel del sistema operativo (por ejemplo, sincronizado / std :: mutex ) o primitivos de hierro ( Atomic * / std :: atomic <*> ) . Y es muy importante ver esta similitud.

En general, estamos desarrollando una aplicación sin carga alta))

¿Cuál es la diferencia entre la aplicación de alta carga y baja latencia?


El término alta carga no refleja completamente los detalles de nuestro trabajo: estamos involucrados en sistemas sensibles a la latencia . Cual es la diferencia Para sistemas altamente cargados, es importante trabajar en promedio con bastante rapidez, haciendo un uso completo de los recursos de hardware. En la práctica, esto significa que cada centésima / milésima /../th millonésima solicitud al sistema puede funcionar muy lentamente, porque nos centramos en valores promedio y no siempre tenemos en cuenta que nuestros usuarios sufren frenos de manera significativa.

Estamos involucrados en sistemas para los cuales el nivel de retraso es crítico. Nuestra tarea es asegurar que el sistema siempre tenga una respuesta predecible. Los sistemas potencialmente sensibles a la latencia pueden no estar muy cargados si los eventos ocurren con poca frecuencia, pero se requiere un tiempo de respuesta garantizado. Y eso no facilita su desarrollo. ¡Todo lo contrario! Los peligros acechan en todas partes. La abrumadora mayoría de los componentes del hardware y software moderno están orientados hacia un buen trabajo "en promedio", es decir. para el rendimiento

Tomar al menos estructuras de datos. Usamos tablas hash, y si ocurre un nuevo hash de toda la estructura de datos en una ruta crítica, esto puede conducir a frenos tangibles para un usuario específico en una sola solicitud. O compilador JIT: optimiza el patrón de código ejecutado con mayor frecuencia, pesimizando el patrón de código que rara vez se ejecuta. ¡Pero la velocidad de este caso raro en particular puede ser muy importante para nosotros!

¿Tal vez este código procesa un tipo raro de pedidos? ¿O alguna situación de mercado inusual que necesita una respuesta rápida? Tratamos de asegurarnos de que la reacción de nuestro sistema a estos eventos potencialmente raros tome un tiempo predecible y, preferiblemente, muy corto.

¿Cómo lograr un tiempo de reacción predecible?


Esta pregunta no se puede responder en dos frases. En una primera aproximación, es importante entender si hay algún tipo de sincronización: sincronizada, reentrantlock o algo de java.util.concurrent . A menudo tienes que usar la sincronización en busy-spin'ah. Usar cualquier primitiva de sincronización siempre es un compromiso. Y es importante comprender cómo funcionan estas primitivas de sincronización y qué compensaciones conllevan. También es importante evaluar cuánta basura genera un fragmento de código en particular. La mejor manera de combatir el recolector de basura es no activarlo. Cuanto menos generemos basura, menos a menudo ejecutaremos un recolector de basura, y más tiempo funcionará el sistema sin su intervención.

También utilizamos una amplia gama de herramientas diferentes que nos permiten analizar no solo los indicadores promedio. Tenemos que analizar muy cuidadosamente cuán lentamente funciona el sistema cada centésima vez, cada milésima vez. Obviamente, estos indicadores serán peores que la mediana o el promedio. Pero es muy importante para nosotros saber cuánto. Y herramientas como Grafana , Prometheus , histogramas HDR y JMH ayudan a mostrar esto.

¿Puedo eliminar inseguro?


A menudo tienes que usar lo que los apologistas llaman una API indocumentada. Estoy hablando del famoso inseguro. Creo que inseguro se ha convertido en una parte de facto de la API pública de las máquinas Java. No tiene sentido negarlo. Inseguro utiliza muchos proyectos que todos utilizamos activamente. Y si lo rechazamos, ¿qué pasará con estos proyectos? O vivirán en la versión anterior de Java, o nuevamente tendrán que gastar mucha energía para reescribir todo esto. ¿La comunidad está lista para esto? ¿Está listo para perder potencialmente decenas de por ciento de productividad? Y lo más importante, ¿a cambio de qué?

Indirectamente, mi opinión confirma una eliminación muy ordenada de los métodos de inseguro: en Java11 se eliminaron los métodos más inútiles de inseguro. Creo que hasta que al menos la mitad de todos los proyectos que usan Unsafe se muevan a otra cosa, Unsafe estará disponible de una forma u otra.

Hay una opinión: Banco + Java = empresa sangrienta osificada?


Nuestro equipo no tiene tales horrores. En Spring, probablemente escribimos diez líneas, y por mí)) Tratamos de no usar grandes tecnologías. Preferimos hacerlo pequeño, ordenado y rápido, para que podamos realizarlo, controlarlo y, si es necesario, modificarlo. Esto último es muy importante para los sistemas (como el nuestro), que tienen requisitos no estándar, que pueden diferir de los requisitos del 90% de los usuarios del marco. Y en el caso de utilizar un marco grande, no podremos transmitir nuestras necesidades a la comunidad o corregir el comportamiento de forma independiente.

En mi opinión, los desarrolladores siempre deberían poder usar todas las herramientas disponibles. Llegué al mundo Java desde C ++ y estoy muy impresionado por la división de la comunidad en aquellos que desarrollan el tiempo de ejecución de la máquina / compilador virtual o la máquina virtual en sí y en los desarrolladores de aplicaciones. Esto se ve claramente en el código de clase JDK estándar. A menudo, los autores de JDK usan una API diferente. Potencialmente, esto significa que no podemos lograr el máximo rendimiento. En general, creo que usar la misma API para escribir tanto la biblioteca estándar como el código de la aplicación es un excelente indicador de la madurez de la plataforma.

Una cosa mas


Creo que es muy importante que todos los desarrolladores sepan cómo, si no toda la pila, al menos la mitad funciona: código Java, código de bytes, componentes internos del tiempo de ejecución y ensamblador de la máquina virtual, hardware, sistema operativo, red. Esto permite una visión más amplia de los problemas.
El rendimiento también vale la pena mencionar. Es muy importante no centrarse en el promedio y mirar siempre los percentiles medios y altos (lo peor de las mediciones 10/100/1000 / ...).

Hablaré de todo esto en una reunión del Grupo de Usuarios de Java el 30 de mayo en San Petersburgo. Reunión con Sergei Melnikov, solo soy yo)) Puedes registrarte aquí .

¿De qué vamos a hablar?

  1. Acerca de la creación de perfiles y el uso del perfilador estándar de Linux y perf: cómo vivir con ellos, qué miden y cómo interpretar sus resultados. Esta será una introducción a los perfiles generales, con consejos, trucos para la vida, cómo exprimir todo lo posible de los perfiladores para que perfilen con la máxima precisión y frecuencia.
  2. Acerca de las características del equipo para obtener un perfil aún más detallado y ver el perfil de eventos raros. Por ejemplo, cuando su código se ejecuta 10 veces más lento cada centésima vez. Ningún perfilador dirá sobre esto. Escribiremos nuestro pequeño generador de perfiles utilizando el mecanismo estándar del kernel de Linux e intentaremos ver el perfil de algún evento raro.

Venga a la reunión, será una gran noche, habrá muchas historias interesantes sobre nuestra plataforma y sobre nuestro idioma favorito.

Hasta luego;)

Source: https://habr.com/ru/post/453688/


All Articles