[c++] C ++ 11 introdujo un modelo de memoria estandarizada. Qué significa eso? ¿Y cómo afectará la programación en C ++?


2 Answers

Daré la analogía con la que entiendo los modelos de consistencia de la memoria (o modelos de memoria, para abreviar). Está inspirado en el artículo seminal de Leslie Lamport "Tiempo, relojes y el ordenamiento de eventos en un sistema distribuido" . La analogía es adecuada y tiene una importancia fundamental, pero puede ser excesiva para muchas personas. Sin embargo, espero que proporcione una imagen mental (una representación pictórica) que facilite el razonamiento sobre los modelos de coherencia de la memoria.

Veamos los historiales de todas las ubicaciones de memoria en un diagrama de espacio-tiempo en el que el eje horizontal representa el espacio de direcciones (es decir, cada ubicación de memoria está representada por un punto en ese eje) y el eje vertical representa el tiempo (veremos que en general, no hay una noción universal de tiempo). El historial de valores que posee cada ubicación de memoria se representa, por lo tanto, mediante una columna vertical en esa dirección de memoria. Cada cambio de valor se debe a que uno de los hilos está escribiendo un nuevo valor en esa ubicación. Por una imagen de memoria , nos referiremos a la agregación / combinación de valores de todas las ubicaciones de memoria observables en un momento determinado por un hilo en particular .

Citando de "Un manual sobre consistencia de memoria y coherencia de caché"

El modelo de memoria intuitiva (y más restrictiva) es la consistencia secuencial (SC) en la que una ejecución multiproceso debería parecerse a un entrelazado de las ejecuciones secuenciales de cada subproceso constituyente, como si los subprocesos se multiplexaran en tiempo en un procesador de núcleo único.

Esa orden de memoria global puede variar de una ejecución del programa a otra y puede no conocerse de antemano. La característica de SC es el conjunto de sectores horizontales en el diagrama de dirección-espacio-tiempo que representa planos de simultaneidad (es decir, imágenes de memoria). En un plano dado, todos sus eventos (o valores de memoria) son simultáneos. Existe una noción de Tiempo Absoluto , en la que todos los hilos coinciden en qué valores de memoria son simultáneos. En SC, en todo momento, solo hay una imagen de memoria compartida por todos los hilos. Es decir, en cada instante, todos los procesadores acuerdan la imagen de la memoria (es decir, el contenido agregado de la memoria). Esto no solo implica que todos los hilos vean la misma secuencia de valores para todas las ubicaciones de memoria, sino también que todos los procesadores observen las mismas combinaciones de valores de todas las variables. Esto es lo mismo que decir que todas las secuencias recuerdan todas las operaciones de memoria (en todas las ubicaciones de memoria) en el mismo orden total.

En los modelos de memoria relajada, cada subproceso dividirá dirección-espacio-tiempo de su propia manera, la única restricción es que las divisiones de cada subproceso no se cruzarán porque todos los subprocesos deben coincidir en el historial de cada ubicación de memoria individual (por supuesto , las rodajas de diferentes hilos pueden, y se cruzarán). No hay una forma universal de dividirlo (no hay foliación privilegiada de espacio-tiempo de direcciones). Las rebanadas no tienen que ser planas (o lineales). Pueden ser curvos y esto es lo que puede hacer que un hilo lea valores escritos por otro hilo fuera del orden en que fueron escritos. Las historias de diferentes ubicaciones de memoria pueden deslizarse (o estirarse) arbitrariamente entre sí cuando se ve por un hilo en particular. . Cada hilo tendrá un sentido diferente de qué eventos (o, de forma equivalente, valores de memoria) son simultáneos. El conjunto de eventos (o valores de memoria) que son simultáneos a un hilo no son simultáneos a otro. Por lo tanto, en un modelo de memoria relajado, todos los subprocesos siguen observando el mismo historial (es decir, secuencia de valores) para cada ubicación de memoria. Pero pueden observar diferentes imágenes de memoria (es decir, combinaciones de valores de todas las ubicaciones de memoria). Incluso si dos ubicaciones de memoria diferentes están escritas por el mismo hilo en secuencia, los dos nuevos valores escritos pueden observarse en orden diferente por otros hilos.

[Imagen de Wikipedia]

Los lectores que estén familiarizados con la Teoría Especial de la Relatividad de Einstein notarán a qué me refiero. Traducir las palabras de Minkowski al reino de los modelos de memoria: el espacio de direcciones y el tiempo son sombras del espacio-tiempo de la dirección. En este caso, cada observador (es decir, hilo) proyectará sombras de eventos (es decir, memoria almacena / cargas) en su propia línea de mundo (es decir, su eje de tiempo) y su propio plano de simultaneidad (su eje dirección-espacio) . Los hilos en el modelo de memoria C ++ 11 corresponden a observadores que se mueven entre sí en relatividad especial. La consistencia secuencial corresponde al espacio-tiempo galileano (es decir, todos los observadores acuerdan un orden absoluto de eventos y un sentido global de simultaneidad).

La semejanza entre los modelos de memoria y la relatividad especial surge del hecho de que ambos definen un conjunto de eventos parcialmente ordenados, a menudo llamado conjunto causal. Algunos eventos (es decir, almacenes de memoria) pueden afectar (pero no verse afectados) por otros eventos. Un hilo de C ++ 11 (u observador en física) no es más que una cadena (es decir, un conjunto totalmente ordenado) de eventos (por ejemplo, cargas de memoria y almacena en direcciones posiblemente diferentes).

En relatividad, se restaura cierto orden a la imagen aparentemente caótica de eventos parcialmente ordenados, ya que el único orden temporal en el que todos los observadores coinciden es en ordenar entre eventos "temporales" (es decir, aquellos eventos que en principio son conectables por cualquier partícula que vaya más lenta que la velocidad de la luz en el vacío). Solo los eventos relacionados con el tiempo se ordenan invariablemente. Tiempo en Física, Craig Callender .

En el modelo de memoria C ++ 11, se usa un mecanismo similar (el modelo de consistencia de adquisición-liberación) para establecer estas relaciones de causalidad locales .

Para proporcionar una definición de consistencia de memoria y una motivación para abandonar SC, citaré de "Un manual sobre consistencia de memoria y coherencia de caché"

Para una máquina de memoria compartida, el modelo de coherencia de memoria define el comportamiento arquitectónico visible de su sistema de memoria. El criterio de corrección para un núcleo de procesador único divide el comportamiento entre " un resultado correcto " y " muchas alternativas incorrectas ". Esto se debe a que la arquitectura del procesador exige que la ejecución de un hilo transforma un estado de entrada dado en un único estado de salida bien definido, incluso en un núcleo fuera de servicio. Los modelos de coherencia de memoria compartida, sin embargo, se refieren a las cargas y los almacenes de varios subprocesos y, por lo general, permiten muchas ejecuciones correctas y no permiten muchas (más) incorrectas. La posibilidad de múltiples ejecuciones correctas se debe a que el ISA permite que se ejecuten varios subprocesos al mismo tiempo, a menudo con muchos entrelazados legales posibles de instrucciones de diferentes subprocesos.

Los modelos de consistencia de memoria relajada o débil están motivados por el hecho de que la mayoría de los ordenamientos de memoria en modelos sólidos son innecesarios. Si un hilo actualiza diez elementos de datos y luego un indicador de sincronización, a los programadores generalmente no les importa si los elementos de datos se actualizan en orden uno con respecto al otro, sino que todos los elementos de datos se actualizan antes de actualizar el indicador (generalmente implementado usando instrucciones FENCE ) Los modelos relajados buscan capturar esta mayor flexibilidad de pedidos y preservar solo las órdenes que los programadores " requieren " para obtener un mayor rendimiento y la corrección de SC. Por ejemplo, en ciertas arquitecturas, cada núcleo utiliza búferes de escritura FIFO para mantener los resultados de las tiendas comprometidas (retiradas) antes de escribir los resultados en las memorias caché. Esta optimización mejora el rendimiento pero viola SC. El búfer de escritura oculta la latencia de dar servicio a un error de tienda. Debido a que las tiendas son comunes, poder evitar el estancamiento en la mayoría de ellas es un beneficio importante. Para un procesador de un solo núcleo, un búfer de escritura puede hacerse arquitectónicamente invisible asegurando que una carga en la dirección A devuelve el valor de la tienda más reciente a A incluso si uno o más almacenamientos en A están en el búfer de escritura. Esto normalmente se realiza omitiendo el valor de la tienda más reciente en A a la carga desde A, donde "más reciente" está determinado por el orden del programa, o bloqueando una carga de A si una tienda en A está en el búfer de escritura . Cuando se usan múltiples núcleos, cada uno tendrá su propio bypass en el búfer de escritura. Sin búferes de escritura, el hardware es SC, pero con búferes de escritura, no lo es, lo que hace que los búferes de escritura sean arquitectónicamente visibles en un procesador multinúcleo.

El reordenamiento de tienda puede ocurrir si un núcleo tiene un búfer de escritura que no es FIFO que permite que las tiendas salgan en un orden diferente al orden en el que ingresaron. Esto podría ocurrir si la primera tienda falla en la memoria caché mientras la segunda visita o si la segunda tienda puede fusionarse con una tienda anterior (es decir, antes de la primera tienda). El reordenamiento de carga de carga también puede ocurrir en núcleos programados dinámicamente que ejecutan las instrucciones fuera del orden del programa. Eso puede comportarse de la misma manera que reordenando tiendas en otro núcleo (¿se puede encontrar un ejemplo de entrelazado entre dos hilos?). Reordenar una carga anterior con una tienda posterior (un reordenamiento de la tienda de carga) puede causar muchos comportamientos incorrectos, como cargar un valor después de liberar el bloqueo que lo protege (si la tienda es la operación de desbloqueo). Tenga en cuenta que los reordenamientos de la carga de la tienda también pueden surgir debido a la derivación local en el búfer de escritura FIFO comúnmente implementado, incluso con un núcleo que ejecuta todas las instrucciones en el orden del programa.

Debido a que la coherencia del caché y la coherencia de la memoria a veces se confunden, es instructivo tener también esta cita:

A diferencia de la coherencia, la coherencia del caché no es visible ni necesaria para el software. Coherence busca hacer que las memorias caché de un sistema de memoria compartida sean funcionalmente invisibles como las memorias caché en un sistema de núcleo único. La coherencia correcta garantiza que un programador no pueda determinar si un sistema tiene cachés y dónde lo tiene al analizar los resultados de las cargas y las tiendas. Esto se debe a que la coherencia correcta garantiza que las memorias caché nunca permitan un comportamiento funcional nuevo o diferente (los programadores aún pueden inferir una estructura de memoria caché probable utilizando información de temporización ). El objetivo principal de los protocolos de coherencia de caché es mantener el invariante de único escritor-múltiples lectores (SWMR) para cada ubicación de memoria. Una distinción importante entre coherencia y coherencia es que la coherencia se especifica en una ubicación por memoria , mientras que la coherencia se especifica con respecto a todas las ubicaciones de memoria.

Continuando con nuestra imagen mental, el invariante SWMR corresponde al requisito físico de que haya como máximo una partícula ubicada en cualquier ubicación, pero puede haber un número ilimitado de observadores en cualquier ubicación.

Question

C ++ 11 introdujo un modelo de memoria estandarizado, pero ¿qué significa eso exactamente? ¿Y cómo afectará la programación en C ++?

Este artículo (de Gavin Clarke, quien cita a Herb Sutter ) dice que,

El modelo de memoria significa que el código C ++ ahora tiene una biblioteca estandarizada para llamar independientemente de quién hizo el compilador y en qué plataforma se está ejecutando. Hay una forma estándar de controlar cómo los diferentes hilos hablan con la memoria del procesador.

"Cuando se habla de dividir [código] entre los diferentes núcleos que están en el estándar, estamos hablando del modelo de memoria. Vamos a optimizarlo sin romper las siguientes suposiciones que la gente hará en el código", dijo Sutter .

Bueno, puedo memorizar este y otros párrafos similares disponibles en línea (ya que tengo mi propio modelo de memoria desde el nacimiento: P) e incluso puedo publicar como respuesta a preguntas hechas por otros, pero para ser honesto, no entiendo exactamente esto. .

Entonces, lo que básicamente quiero saber es que los programadores de C ++ solían desarrollar aplicaciones de subprocesos múltiples incluso antes, entonces, ¿qué importa si se trata de subprocesos POSIX, o subprocesos de Windows, o subprocesos de C ++ 11? ¿Cuales son los beneficios? Quiero entender los detalles de bajo nivel.

También me da la sensación de que el modelo de memoria C ++ 11 está relacionado de algún modo con el soporte multihilo de C ++ 11, ya que a menudo los veo juntos. Si es así, ¿cómo exactamente? ¿Por qué deberían estar relacionados?

Como no sé cómo funciona el funcionamiento interno de multihilo y qué modelo de memoria significa en general, ayúdame a comprender estos conceptos. :-)




Si usa mutexes para proteger todos sus datos, no debería preocuparse. Los mutexes siempre han proporcionado suficientes garantías de orden y visibilidad.

Ahora, si usó algoritmos atómicos o sin bloqueo, debe pensar en el modelo de memoria. El modelo de memoria describe con precisión cuándo los atómicos proporcionan garantías de orden y visibilidad, y proporciona vallas portátiles para las garantías codificadas a mano.

Anteriormente, los átomos se realizarían utilizando intrínsecos del compilador o alguna biblioteca de nivel superior. Las cercas se habrían hecho usando instrucciones específicas de CPU (barreras de memoria).




Significa que el estándar ahora define multi-threading, y define lo que sucede en el contexto de múltiples hilos. Por supuesto, las personas usaron diferentes implementaciones, pero eso es como preguntar por qué deberíamos tener una std::string cuando todos podríamos estar usando una clase de string enrollada en el hogar.

Cuando se habla de hilos POSIX o hilos de Windows, esto es un poco ilusorio ya que en realidad se trata de hilos x86, ya que es una función de hardware que se ejecuta simultáneamente. El modelo de memoria C ++ 0x ofrece garantías, ya sea que esté en x86, ARM o MIPS , o cualquier otra cosa que se le ocurra.




Related