¿Por qué el equipo de LMAX usó Java y diseñó la arquitectura para evitar GC a toda costa?

24

¿Por qué el equipo de LMAX diseñó el Disruptor LMAX en Java pero todos sus puntos de diseño para minimizar el uso de GC? Si uno no quiere que se ejecute GC, ¿por qué usar un lenguaje de recolección de basura?

Sus optimizaciones, el nivel de conocimiento del hardware y el pensamiento que ponen son simplemente impresionantes, pero ¿por qué Java?

No estoy en contra de Java ni nada, pero ¿por qué un lenguaje GC? ¿Por qué no usar algo como D o cualquier otro lenguaje sin GC pero permite un código eficiente? ¿Es que el equipo está más familiarizado con Java o Java posee alguna ventaja única que no estoy viendo?

Digamos que lo desarrollan usando D con administración manual de memoria, ¿cuál sería la diferencia? Tendrían que pensar bajo nivel (que ya son), pero pueden exprimir el mejor rendimiento del sistema como es nativo.


fuente
66
Sé muy poco sobre este proyecto, pero parece que es algún tipo de marco sobre el que otros pueden construir. Y si logra escribir eso en Java (y permite que otros codifiquen en Java y coseche los beneficios), entonces tendrá una "base de clientes" MUCHO más grande que si lo hubiera escrito en D.
Joachim Sauer
66
@kadaj: realmente no importa si el consumidor es público o interno: si lo hace accesible en un idioma ampliamente conocido, será más útil, incluso para el desarrollo interno. Si comienza su argumento (hipotético) con: "Suponga que todos conocen D tan bien como conocen Java, ...", entonces probablemente se esté perdiendo algo.
Joachim Sauer
66
A algunas personas les gusta usar martillos para todo tipo de problemas. Tienes un borde áspero que quieres cepillar, golpéalo con tu martillo hasta que esté suave. Tienes un tornillo que necesitas meter, golpea con un martillo hasta que esté adentro. Tienes un adorno delicado que necesitas lijar, golpea con un martillo y luego culpa al ornamento por "chupar". C o C ++ habría sido una mejor opción que D, si solo fuera por la base de conocimiento existente. No estoy seguro de por qué incluso mencionaste D como ejemplo TBH.
gbjbaanb
2
@gbjbaanb Mencioné D porque proporciona recolección de basura (en casos donde son necesarias abstracciones de alto nivel y jugar con la memoria es demasiado difícil para el cerebro) pero también permite el manejo manual de la memoria con malloc estilo C y gratis. D es algo así como Objective-C con ARC (sin GC real) pero mejor que eso. Pero sí, C / C ++ encajaría bien.
44
@kadaj Veo que has estado recibiendo críticas por mencionar a D, pero quiero decir que estoy decepcionado con el tono que otros están usando y exponer por qué creo que D es central en la pregunta en cuestión. Si bien D no se usa ampliamente, D proporciona algunas construcciones de alto nivel que podría esperar encontrar en Java o C #, pero no en C ++ (al menos a la antigua). Todavía proporciona la mezcla de administrado y no administrado, ¡que es casi el único idioma que sé para hacer eso! Por lo tanto, D no es solo una elección favorita, sino que tiene objetivos que coinciden con las preguntas originales sobre GC.
J Trana

Respuestas:

20

Porque hay una gran diferencia entre optimizar el rendimiento y desactivar por completo una seguridad

Al reducir la cantidad de GC, su marco es más receptivo y puede ejecutarse (presumiblemente) más rápido. Ahora, optimizar para el recolector de basura no significa que nunca hagan una recolección de basura. Simplemente significa que lo hacen con menos frecuencia, y cuando lo hacen, corre muy rápido. Ese tipo de optimización incluye:

  1. Minimizando el número de objetos que se mueven a un espacio de sobreviviente (es decir, que sobrevivió al menos a una recolección de basura) mediante el uso de pequeños objetos desechables. Los objetos que se movieron al espacio de sobrevivientes son más difíciles de recolectar y una recolección de basura aquí en algún momento implica congelar toda la JVM.
  2. Para empezar, no asigne demasiados objetos. Esto puede ser contraproducente si no tiene cuidado, ya que los objetos de la generación joven son muy baratos de asignar y recolectar.
  3. Asegúrese de que el objeto nuevo apunte al antiguo (y no al revés) para que el objeto joven sea fácil de recolectar, ya que no hay referencia a ellos que haga que se mantengan

Cuando desconecta el rendimiento, generalmente ajusta un "punto caliente" muy específico mientras ignora el código que no se ejecuta con frecuencia. Si hace eso en Java, puede dejar que el recolector de basura se encargue de esos rincones oscuros (ya que no hará mucha diferencia) mientras optimiza con mucho cuidado el área que se ejecuta en un circuito cerrado. Por lo tanto, puede elegir dónde optimizar y dónde no, y así puede enfocar su esfuerzo donde sea importante.


Ahora, si apaga completamente la recolección de basura, entonces no puede elegir. Debe deshacerse manualmente de cada objeto, nunca. ¿Se llama ese método como máximo una vez al día? En Java, puede dejarlo, ya que su impacto en el rendimiento es insignificante (puede estar bien dejar que ocurra un GC completo cada mes). En C ++, todavía está perdiendo recursos, por lo que debe cuidar incluso ese método oscuro. Por lo tanto, debe pagar el precio de la gestión de recursos en cada parte de su aplicación, mientras que en Java puede concentrarse.


Pero empeora.

¿Qué sucede si tiene un error, digamos en un rincón oscuro de su aplicación al que solo se accede el lunes en luna llena? Java tiene una fuerte garantía de seguridad. Hay poco o ningún "comportamiento indefinido". Si usa algo incorrecto, se genera una excepción, su programa se detiene y no se produce corrupción de datos. Así que estás bastante seguro de que nada malo puede suceder sin que te des cuenta.

Pero en algo como D, puede tener un mal acceso al puntero, o un desbordamiento del búfer, y puede dañar su memoria, pero su programa no lo sabrá (desactivó la seguridad, ¿recuerda?) Y seguirá funcionando con su incorrecto datos, y hacer algunas cosas bastante desagradables y corromper sus datos, y no lo sabe, y a medida que ocurre más corrupción, sus datos se vuelven cada vez más incorrectos, y de repente se rompen, y estaban en una aplicación crítica para la vida, y se produjo un error en el cálculo de un cohete, por lo que no funciona, explota el cohete y alguien muere, y su empresa está en la primera plana de cada periódico y su jefe le señala con el dedo que dice "Usted está El ingeniero que sugirió que usáramos D para optimizar el rendimiento, ¿por qué no pensó en la seguridad?". Y es tu culpa. Usted mató a esas personas con su estúpido intento en el rendimiento.


Ok, ok, la mayoría de las veces es mucho menos dramático que eso. Pero incluso una aplicación crítica para el negocio o simplemente una aplicación de GPS o, digamos, un sitio web de salud del gobierno puede tener algunas consecuencias bastante negativas si tiene errores. Usar un lenguaje que los prevenga por completo o que falle rápido cuando suceden suele ser una muy buena idea.

Hay un costo para desactivar una seguridad. Ser nativo no siempre tiene sentido. A veces es mucho más simple y seguro optimizar un poco un lenguaje seguro que utilizarlo todo para un idioma en el que puedas dispararte en el pie a lo grande. La corrección y la seguridad en muchos casos superan los pocos nanosegundos que habría desechado al eliminar el GC por completo. Disruptor se puede utilizar en esa situación, por lo que creo que LMAX-Exchange hizo la llamada correcta.

Pero, ¿qué pasa con D en particular? Tienes un GC si quieres para las esquinas oscuras, y el subconjunto SafeD (que no conocía antes de la edición) elimina el comportamiento indefinido (¡si recuerdas usarlo!).

Bueno, en ese caso es una simple cuestión de madurez. El ecosistema de Java está lleno de herramientas bien escritas y bibliotecas maduras (mejor para el desarrollo). Muchos más desarrolladores conocen Java que D (mejor para mantenimiento). Buscar un lenguaje nuevo y no tan popular para algo tan crítico como una aplicación financiera no hubiera sido una buena idea. Con un lenguaje menos conocido, si tiene un problema, pocos pueden ayudarlo, y las bibliotecas que encuentra tienden a tener más errores ya que estuvieron expuestos a menos personas.

Así que mi último punto aún es válido: si desea evitar problemas con consecuencias nefastas, quédese con opciones seguras. En este punto de la vida de D, sus clientes son las pequeñas empresas nuevas listas para asumir riesgos locos. Si un problema puede costar millones, es mejor mantenerse más lejos en la curva de la campana de la innovación .

Laurent Bourgault-Roy
fuente
2
La publicación original específicamente llama a D. En realidad, existe una gran diferencia entre C ++ y D con respecto a la granularidad de elección. Incluso si elige ir completamente administrado en el subconjunto SafeD, creo que tiene un poco más de control sobre ciertos aspectos de la recopilación y el tiempo (habilitar / deshabilitar, recopilar, minimizar). ¡Mira las estrategias de Digital Mars para la gestión de la memoria!
J Trana
2
lmax deliberadamente deja de lado parte de la seguridad que brinda Java
James
Esta sería una gran respuesta, excepto que Java no tiene licencia para software de misión crítica. Si tiene un reactor anuclear, estará escrito en C ++ y no en Java, lo que arroja un poco el aspecto de "seguridad".
gbjbaanb
@gbjbaanb, [cita requerida]. Los estándares / pautas de confiabilidad que he visto recomiendan primero evitar C / C ++ a favor de otros lenguajes; y si entra en ellos, entonces use versiones altamente restringidas de los idiomas (MISRA, etc.). Y una vez que acepte las restricciones, no veo por qué no podría hacer lo mismo con ningún otro idioma. Si estaba pensando en la mención de Java Licence de "no para instalaciones nucleares" en la sección RESTRICCIONES, parece que eso cambió hace algún tiempo y ahora en cambio solo dice algo parecido a "tenga cuidado, no es nuestra responsabilidad". Aún así, supongo que el (...)
hmijail
(...) la redacción original era como las licencias de gcc y clang: no hay garantías para ningún propósito específico. Por lo tanto, no los usaría para algo que requiera confiabilidad, y en su lugar necesitaría usar algún compilador certificado, si no llega a un lenguaje específico para el trabajo (¿Ada?).
hmijail
4

Parece que la razón por la que está escrito en Java es que tienen experiencia en Java internamente y probablemente fue escrito (aunque todavía está en desarrollo activo) antes de que C ++ actuara junto con C ++ 0x / 11.

Su código es en realidad solo Java por su nombre, usan sun.misc.Unsefe bastante, lo que derrota el punto de Java y supuestamente brinda seguridad. He escrito un puerto C ++ del Disruptor y supera el código Java que envían (no pasé mucho tiempo ajustando la JVM).

Dicho esto, los principios que sigue el disruptor no son específicos del lenguaje, por ejemplo, no espere un código C ++ de baja latencia que asigne o libere del montón.

James
fuente
¿Puedes señalar tu implementación? Vi un par de tales reimplementaciones que afirmaron un mayor rendimiento, pero ambas engañaron con simplificaciones: por ejemplo, conectar 1 productor + 1 consumidor en lugar de ser capaz de producir / producir múltiples productores como el Disruptor original. El autor del Disruptor mismo mencionó en un hilo de Grupos de Google que el rendimiento podría mejorarse mediante parámetros de cableado en la versión Java.
hmijail
4

Esta pregunta establece una premisa incorrecta como un hecho, luego hace un argumento sobre esa premisa incorrecta.

Vamos a profundizar en esto ... "todos sus puntos de diseño para minimizar el uso de GC", simplemente no es cierto. La innovación en el disruptor tiene poco que ver con GC. El disruptor funciona porque su diseño considera inteligentemente cómo funcionan las computadoras modernas, algo que es mucho menos común de lo que uno podría esperar. Vea la charla de Cliff Click http://www.azulsystems.com/events/javaone_2009/session/2009_J1_HardwareCrashCourse.pdf para una discusión.

Es bien sabido que LMax son clientes de Azul. Sé de primera mano que con los GC azules simplemente no es un problema, incluso con montones de 175 GB.

peterbooth
fuente
Hay un grano de verdad en esto. Reinician la VM todas las noches para evitar una colección importante. Eso es lo que Martin Fowler escribió, de todos modos, y no es tonto: "Al igual que el resto del sistema, los disruptores se hacen rebotar de la noche a la mañana. Este rebote se realiza principalmente para borrar la memoria y así hay menos posibilidades de un costoso evento de recolección de basura durante el comercio". martinfowler.com/articles/lmax.html
JimmyJames
2
No exactamente. Solíamos activar un GC manual cada noche en una brecha comercial de 5 minutos, y lo ajustamos para que fuera el único GC importante en un día. Eso se volvió redundante con Azul Zing. (Fuente: trabajé en LMAX hasta hace poco)
Tom Johnson
@TomJohnson Love obteniendo la primicia. ¿Estás diciendo que la descripción de Martin Fowler es incorrecta? ¿Es posible que la solución haya evolucionado con el tiempo?
JimmyJames
2
Estoy diciendo que no estaba precisamente correcto en algunos detalles menores. Nunca hicimos rebotar nuestros sistemas a diario, pero sí hicimos una limpieza al final del día.
Tom Johnson
3

Tendrían que pensar bajo nivel

Arriba hace la mitad de la respuesta que estás buscando. Puede encontrar otra mitad para completar el razonamiento no más allá del blog LMAX :

Si bien es muy eficiente, puede provocar una serie de errores, ya que es muy fácil de fastidiar ...

Como admitieron los desarrolladores de LMAX, un código como ese podría ser bastante difícil de desarrollar, comprender y depurar, incluso en Java. Ir a un nivel inferior más allá de donde están ahora solo exacerbará este problema, como se señala en el artículo de Wikipedia sobre lenguajes de programación de bajo nivel :

Se puede hacer que un programa escrito en un lenguaje de bajo nivel se ejecute muy rápidamente y con una huella de memoria muy pequeña; un programa equivalente en un lenguaje de alto nivel será más pesado. Los lenguajes de bajo nivel son simples, pero se consideran difíciles de usar, debido a los numerosos detalles técnicos que deben recordarse .

En comparación, un lenguaje de programación de alto nivel aísla la semántica de ejecución de una arquitectura de computadora de la especificación del programa, lo que simplifica el desarrollo ...

mosquito
fuente
3

Si utiliza Java como lenguaje de sintaxis y evita sus bibliotecas JDK, puede ser tan rápido como un lenguaje compilado que no sea GC. GC no es adecuado para sistemas en tiempo real, pero es posible desarrollar sistemas en Java que no dejen basura. Como resultado, el GC nunca se dispara.

Creemos que el lenguaje y la plataforma Java tienen muchas ventajas sobre C / C ++ y hemos desarrollado y comparado algunos componentes Java de latencia ultra baja para probarlo. Hablamos sobre las técnicas para hacerlo en este artículo: Desarrollo Java sin GC .

rdalmeida
fuente
2
Hay recolectores de basura adecuados para sistemas en tiempo real. El recopilador predeterminado de la JVM puede no serlo, pero eso no significa que GC en general no sea adecuado para el tiempo real. Pero plain malloc/freetampoco es adecuado para el tiempo real, ya que el tiempo de asignación no tiene límites debido a la fragmentación.
Doval
1
Abogamos por el uso de conjuntos de objetos rápidos para todo, de modo que no ocurra ninguna asignación después del calentamiento.
rdalmeida
2

LMAX es una biblioteca de mensajería entre subprocesos de alto rendimiento.

Para ser útil, alguien más tiene que escribir el código para que cada hilo haga un trabajo útil. Dado que es muy probable que el código esté en Java o C # y luego hay muy pocas opciones de lenguaje que interactúen bien con ellos.

Usar C o C ++ no es una buena opción a menos que desee limitar a sus usuarios a un único sistema operativo, ya que no hay un modelo de subprocesos definido en ellos.

Java es el estándar para mucho desarrollo de software en estos días, por lo que, a menos que tenga una buena razón de lo contrario, tiende a ser la mejor opción. (Cuando en Roma, haz como los romanos…)

Escribir software de alto rendimiento en Java (o C #) a menudo se hace para probar un punto ...

Ian
fuente
1
El nuevo estándar C ++ 11 admite subprocesos múltiples ...
Casey
@Casey, ¿y cuántos compiladores de C ++ del mundo real lo usan? Y cuánto cuestan estos compiladores. Tal vez en 20 años sea útil, hasta entonces no puede depender de ello.
Ian
Disruptor usa sun.misc. Inseguro bastante, lo que demuestra que realmente no se puede escribir código de baja latencia en Java sin sumergir el dedo del pie en la tierra C
James
3
Gcc admite subprocesos de C ++ y es gratis
James
@ Ian: 2 años después y todos los compiladores utilizados en general lo admiten;). Incluso los que son gratis.
Rutix