memory - sistema - ¿Cómo maneja la memoria compartida frente a la transmisión de mensajes grandes estructuras de datos?
mi bios no me deja aumentar la memoria de video (10)
¿Qué es una gran estructura de datos?
Una persona grande es otra persona pequeña.
La semana pasada hablé con dos personas: una persona creaba dispositivos integrados, usaba la palabra "grande", le pregunté qué quería decir, dijo más de 256 KBytes, más tarde en la misma semana en que un tipo hablaba de distribución de medios. la palabra "grande" le pregunté a qué se refería; pensó un momento y dijo "no cabe en una máquina", digan 20-100 TBytes
En términos de Erlang "grande" podría significar "no cabe en la memoria RAM", por lo que con 4 GBytes de estructuras de datos RAM> 100 MBytes podrían considerarse grandes, copiar una estructura de datos de 500 MB puede ser un problema. Copiar pequeñas estructuras de datos (digamos <10 MBytes) nunca es un problema en Erlang.
Las estructuras de datos muy grandes (es decir, las que no caben en una máquina) tienen que copiarse y "rayarse" en varias máquinas.
Entonces supongo que tienes lo siguiente:
Las pequeñas estructuras de datos no son un problema, ya que son pequeños los tiempos de procesamiento de datos son rápidos, la copia es rápida y demás (solo porque son pequeños)
Las estructuras de Big Data son un problema, porque no caben en una sola máquina, por lo que la copia es esencial.
Al observar el enfoque de concurrencia de Go y Erlang, noté que ambos dependen del envío de mensajes.
Este enfoque obviamente alivia la necesidad de bloqueos complejos porque no hay un estado compartido.
Sin embargo, considere el caso de muchos clientes que desean acceso paralelo de solo lectura a una única estructura de datos grande en la memoria, como una matriz de sufijos.
Mis preguntas:
¿Será más rápido usar el estado compartido y usar menos memoria que el paso de mensajes, ya que los bloqueos serán en su mayoría innecesarios porque los datos son de solo lectura y solo necesitan existir en una sola ubicación?
¿Cómo se abordaría este problema en un contexto de transmisión de mensajes? ¿Habría un solo proceso con acceso a la estructura de datos y los clientes simplemente necesitarían solicitar datos de manera secuencial? O, si es posible, ¿se fragmentarían los datos para crear varios procesos que contengan fragmentos?
Dada la arquitectura de las CPU y la memoria modernas, ¿hay mucha diferencia entre las dos soluciones, es decir, la memoria compartida puede leerse en paralelo por múltiples núcleos, lo que significa que no hay ningún cuello de botella de hardware que de otro modo haría que ambas implementaciones realizaran lo mismo?
El otro paradigma concurrente es STM, memoria transaccional de software. Los ref de Clojure están recibiendo mucha atención. Tim Bray tiene una buena serie que explora los mecanismos concurrentes de erlang y clojure
http://www.tbray.org/ongoing/When/200x/2009/09/27/Concur-dot-next
http://www.tbray.org/ongoing/When/200x/2009/12/01/Clojure-Theses
El problema en este momento es que la coherencia y la coherencia de la línea de caché pueden ser tan caras como la copia de una estructura de datos más simple (por ejemplo, unos cientos de bytes).
La mayoría de las veces, un nuevo algoritmo inteligente de múltiples subprocesos escrito que intenta eliminar la mayor parte del bloqueo siempre será más rápido, y mucho más rápido con estructuras de datos modernas y libres de bloqueos. Especialmente cuando tiene sistemas de caché bien diseñados como el multihilo de nivel de chip Niagara de Sun.
Si su sistema / problema no se divide fácilmente en unos pocos y simples accesos de datos, entonces tiene un problema. Y no todos los problemas pueden resolverse mediante el envío de mensajes. Esta es la razón por la que todavía hay algunas supercomputadoras basadas en Itanium que se venden porque tienen terabytes de RAM compartida y hasta 128 CPU que funcionan en la misma memoria compartida. Son un orden de magnitud más caros que un clúster x86 convencional con la misma potencia de CPU, pero no es necesario que descomponga sus datos.
Otra razón no mencionada hasta ahora es que los programas pueden ser mucho más fáciles de escribir y mantener cuando se usa multi-threading. El paso de mensajes y el enfoque de nada compartido lo hacen aún más fácil de mantener.
Como ejemplo, Erlang nunca fue diseñado para hacer las cosas más rápido, sino que usa una gran cantidad de subprocesos para estructurar datos complejos y flujos de eventos.
Supongo que este fue uno de los puntos principales en el diseño. En el mundo web de google, normalmente no le importa el rendimiento, siempre que pueda ejecutarse en paralelo en la nube. Y con el mensaje que pasa, lo ideal es que simplemente agregue más computadoras sin cambiar el código fuente.
En Erlang, todos los valores son inmutables, por lo que no es necesario copiar un mensaje cuando se envía entre procesos, ya que no se puede modificar de todos modos.
En Go, la transferencia de mensajes es por convención: no hay nada que evite que envíe a alguien un puntero sobre un canal, luego modifique los datos apuntados, solo convenciones, así que una vez más no hay necesidad de copiar el mensaje.
Habitualmente, envía mensajes a los idiomas que pasan (esto es especialmente fácil en erlang, ya que tiene variables inmutables) optimiza la copia de datos entre los procesos (por supuesto, solo procesos locales: querrás pensar el patrón de distribución de tu red sabiamente), así que no mucho un problema.
La mayoría de los procesadores modernos usan variantes del protocolo MESI . Debido al estado compartido, Pasar datos de solo lectura entre diferentes hilos es muy barato. Sin embargo, los datos compartidos modificados son muy caros, porque todas las demás cachés que almacenan esta línea de caché deben invalidarlo.
Entonces, si tiene datos de solo lectura, es muy barato compartirlos entre hilos en lugar de copiarlos con mensajes. Si ha leído principalmente datos, puede ser costoso compartirlos entre hilos, en parte debido a la necesidad de sincronizar el acceso, y en parte porque las escrituras destruyen el comportamiento amigable de caché de los datos compartidos.
Las estructuras de datos inmutables pueden ser beneficiosas aquí. En lugar de cambiar la estructura de datos real, simplemente hace uno nuevo que comparte la mayoría de los datos anteriores, pero con las cosas cambiadas que necesita cambiar. Compartir una sola versión es barato, ya que todos los datos son inmutables, pero aún puede actualizarse a una nueva versión de manera eficiente.
Tenga en cuenta que sus preguntas son técnicamente no sensoriales porque el envío de mensajes puede utilizar el estado compartido, por lo que supondré que se refiere al paso de mensajes con copia profunda para evitar el estado compartido (como lo hace Erlang en la actualidad).
¿Será más rápido usar el estado compartido y usar menos memoria que el paso de mensajes, ya que los bloqueos serán en su mayoría innecesarios porque los datos son de solo lectura y solo necesitan existir en una sola ubicación?
Usar el estado compartido será mucho más rápido.
¿Cómo se abordaría este problema en un contexto de transmisión de mensajes? ¿Habría un solo proceso con acceso a la estructura de datos y los clientes simplemente necesitarían solicitar datos de manera secuencial? O, si es posible, ¿se fragmentarían los datos para crear varios procesos que contengan fragmentos?
Cualquiera de los enfoques puede ser utilizado.
Dada la arquitectura de las CPU y la memoria modernas, ¿hay mucha diferencia entre las dos soluciones, es decir, la memoria compartida puede leerse en paralelo por múltiples núcleos, lo que significa que no hay ningún cuello de botella de hardware que de otro modo haría que ambas implementaciones realizaran lo mismo?
La copia no es amigable con la memoria caché y, por lo tanto, destruye la escalabilidad en multinúcleas porque empeora la disputa por el recurso compartido que es la memoria principal.
En última instancia, el envío de mensajes al estilo Erlang está diseñado para la programación concurrente, mientras que sus preguntas sobre el rendimiento del rendimiento están realmente dirigidas a la programación paralela. Estos son dos temas bastante diferentes y la superposición entre ellos es pequeña en la práctica. Específicamente, la latencia es, por lo general, tan importante como el rendimiento en el contexto de la programación concurrente y el paso de mensajes al estilo Erlang es una gran forma de lograr perfiles de latencia deseables (es decir, consistentemente bajas latencias). El problema con la memoria compartida no es tanto la sincronización entre lectores y escritores, sino la gestión de memoria de baja latencia.
Una cosa que debemos tener en cuenta es que el modelo de simultaneidad de Erlang NO especifica realmente que los datos en los mensajes se deben copiar entre los procesos, sino que el envío de mensajes es la única forma de comunicarse y no hay un estado compartido. Como todos los datos son inmutables, lo cual es fundamental, una implementación puede muy bien no copiar los datos sino enviar una referencia a ellos. O puede usar una combinación de ambos métodos. Como siempre, no existe la mejor solución y se pueden hacer concesiones al elegir cómo hacerlo.
El BEAM utiliza la copia, a excepción de los grandes binarios donde envía una referencia.
Una solución que no se ha presentado aquí es la replicación maestro-esclavo. Si tiene una estructura de datos grande, puede replicar los cambios en ella a todos los esclavos que realizan la actualización en su copia.
Esto es especialmente interesante si se quiere escalar a varias máquinas que ni siquiera tienen la posibilidad de compartir la memoria sin configuraciones muy artificiales (mmap de un dispositivo de bloque que lee / escribe desde la memoria de una computadora remota).
Una variante de esto es tener un administrador de transacciones que pida amablemente que actualice la estructura de datos replicada, y se asegurará de que atienda una sola solicitud de actualización al mismo tiempo. Este es más el modelo de mnesia para la replicación maestro-maestro de los datos de tabla de mnesia, que califican como "estructura de datos de gran tamaño".
Sí, el estado compartido podría ser más rápido en este caso. Pero solo si puedes renunciar a los bloqueos, y esto solo es factible si es absolutamente de solo lectura. si es ''principalmente de solo lectura'', entonces necesitas un candado (a menos que te las arregles para escribir estructuras libres de cerraduras, ten en cuenta que son incluso más complicadas que las cerraduras), y entonces sería muy difícil hacerlo funcionar como rápido como una buena arquitectura para pasar mensajes.
Sí, podría escribir un ''proceso de servidor'' para compartirlo. Con procesos realmente livianos, no es más pesado que escribir una API pequeña para acceder a los datos. Piensa como un objeto (en sentido OOP) que ''posee'' los datos. Dividir los datos en fragmentos para mejorar el paralelismo (llamado ''sharding'' en los círculos DB) ayuda en grandes casos (o si los datos están en almacenamiento lento).
Incluso si NUMA se está generalizando, usted todavía tiene más y más núcleos por celda NUMA. Y una gran diferencia es que se puede pasar un mensaje entre solo dos núcleos, mientras que un bloqueo debe eliminarse de la memoria caché en TODOS los núcleos, limitándolo a la latencia del bus entre células (incluso más lento que el acceso a la memoria RAM). En todo caso, el estado compartido / bloqueos se vuelve cada vez más inviable.
en resumen ... acostúmbrate a procesar mensajes y procesos de servidor, es furor.
Editar : revisando esta respuesta, quiero agregar una frase que se encuentra en la documentación de Go:
comparta memoria al comunicarse, no se comunique compartiendo memoria.
la idea es: cuando tienes un bloque de memoria compartido entre subprocesos, la forma típica de evitar el acceso simultáneo es usar un bloqueo para arbitrar. El estilo Go es pasar un mensaje con la referencia, un hilo solo accede a la memoria cuando recibe el mensaje. Se basa en cierta medida de disciplina de programador; pero da como resultado un código de aspecto muy limpio que se puede corregir fácilmente, por lo que es relativamente fácil de depurar.
la ventaja es que no tiene que copiar grandes bloques de datos en cada mensaje y no tiene que vaciar eficazmente los cachés como en algunas implementaciones de bloqueo. Todavía es algo temprano para decir si el estilo conduce a diseños de mayor rendimiento o no. (especialmente dado que el tiempo de ejecución Go actual es algo ingenuo en la programación de hilos)