Compara e intercambia C++ 0x
multithreading gcc (1)
La respuesta aquí no es trivial. Exactamente lo que sucede y lo que se entiende depende de muchas cosas. Para una comprensión básica de la coherencia / memoria del caché, quizás mis entradas recientes en el blog sean útiles:
- Reordenamiento de la CPU: ¿qué se está reordenando en realidad?
- Memoria de la CPU: ¿por qué necesito un mutex?
Pero aparte de eso, déjame intentar responder algunas preguntas. En primer lugar, la siguiente instrucción tiene muchas esperanzas en cuanto a lo que se admite.
compare_swap( C& expected, C desired,
memory_order success, memory_order failure )
Las arquitecturas no podrán implementar esto exactamente como lo solicitó. Cuando especifica memory_order, especifica cómo puede funcionar la reordenación. Para usar los términos de intel, especificará qué tipo de cercado desea, hay tres, la valla completa, la cerca de carga y la cerca de la tienda. El hecho de que desee una valla particular en esa operación no significa que sea compatible, en lo que espero que siempre vuelva a ser una valla completa.
Es probable que el compilador use la instrucción CMPXCHG
para implementar la llamada. Si ha especificado algo más que relajado, marcará esto con un lock
para indicar que la función debe ser atómica. Si esto es "libre de bloqueo" depende mucho de lo que está pensando en términos de un "bloqueo".
En términos de sincronización de memoria, debes entender cómo funciona la coherencia de caché (mi blog puede ayudar un poco). Las nuevas CPU utilizan una arquitectura ccNUMA (anteriormente SMP). Esencialmente, la "vista" en la memoria nunca se sale de sincronización. Las cercas utilizadas en el código no obligan a que se produzca ningún enrojecimiento per se. Si dos núcleos tienen la misma ubicación de memoria en caché en una línea de caché, uno se marcará como sucio y el otro se volverá a cargar según sea necesario. Una explicación muy simple para un proceso muy complejo
Para responder a su última pregunta, siempre debe usar la semántica de la memoria que lógicamente necesita para ser correcta. La mayoría de las arquitecturas no admitirán todas las combinaciones que use en su programa. Sin embargo, en muchos casos obtendrá excelentes optimizaciones, especialmente en los casos en que el pedido que solicitó está garantizado sin una valla (que es bastante común).
- Respuestas a algunos comentarios:
Debe distinguir entre lo que significa ejecutar una instrucción de escritura y escribir en una ubicación de memoria. Esto es lo que trato de explicar en mi blog. En el momento en que el "0" está comprometido con 0x100, todos los núcleos ven ese cero. Escribir enteros también es atómico, es decir, incluso sin un bloqueo, cuando escribe en una ubicación todos los núcleos tendrán inmediatamente ese valor si desean usarlo.
El problema es que para usar el valor que probablemente haya cargado en un registro primero, cualquier cambio en la ubicación después de eso obviamente no tocará el registro. Esta es la razón por la cual uno necesita mutexes a pesar de una memoria coherente de caché.
En cuanto a los reclamos contradictorios, generalmente verá todo tipo de reclamos. Si son contradictorios viene exactamente a lo que "ver" "cargar" significa "ejecutar" en el contexto. Si escribe "1" a 0x100, ¿significa eso que usted ejecutó la instrucción de escritura o la CPU realmente confirmó ese valor? La diferencia proviene de la reordenación. La CPU puede retrasar la escritura del "1", pero puede estar seguro de que en el momento en que finalmente confirma que "1" todos los núcleos lo ven. Las vallas controlan este orden.
De la propuesta C ++ 0x en C ++ Tipos y operaciones atómicos:
29.1 Orden y consistencia [atomics.order]
Agregue una nueva subcláusula con los siguientes párrafos.
La enumeración
memory_order
especifica el orden de sincronización de memoria regular (no atómico) detallado como se define en [la nueva sección añadida por N2334 o su sucesor adoptado] y puede proporcionar el orden de operación. Sus valores enumerados y sus significados son los siguientes.
memory_order_relaxed
La operación no ordena la memoria.
memory_order_release
Realiza una operación de liberación en las ubicaciones de memoria afectadas, lo que hace que las escrituras de memoria regulares sean visibles para otros hilos a través de la variable atómica a la que se aplica.
memory_order_acquire
Realiza una operación de adquisición en las ubicaciones de memoria afectadas, lo que hace que las escrituras de memoria regulares en otros hilos lanzados a través de la variable atómica a la que se aplica, sean visibles para el hilo actual.
memory_order_acq_rel
La operación tiene tanto semántica de adquisición como de liberación.
memory_order_seq_cst
La operación tiene tanto semántica de adquisición como de liberación y, además, tiene un orden de operación secuencialmente consistente.
Más bajo en la propuesta:
bool A::compare_swap( C& expected, C desired, memory_order success, memory_order failure ) volatile
donde uno puede especificar el orden de memoria para el CAS.
Tengo entendido que " memory_order_acq_rel
" solo sincronizará necesariamente las ubicaciones de memoria que se necesitan para la operación, mientras que otras ubicaciones de memoria pueden permanecer sin sincronizar (no se comportará como una valla de memoria).
Ahora, mi pregunta es: si elijo " memory_order_acq_rel
" y aplico compare_swap
a tipos enteros, por ejemplo, enteros, ¿cómo se traduce esto típicamente en código de máquina en procesadores de consumo modernos como Intel i7 multinúcleo? ¿Qué pasa con las otras arquitecturas comúnmente utilizadas (x64, SPARC, ppc, brazo)?
En particular (asumiendo un compilador concreto, digamos gcc):
- ¿Cómo comparar y cambiar una ubicación entera con la operación anterior?
- ¿Qué secuencia de instrucciones producirá tal código?
- ¿La operación está libre de bloqueo en i7?
- ¿Funcionará tal operación un protocolo de coherencia de caché completo, sincronizando cachés de diferentes núcleos de procesador como si fuera una valla de memoria en i7? ¿O solo sincronizará las ubicaciones de memoria que necesita esta operación?
- Relacionado con la pregunta anterior: ¿hay alguna ventaja de rendimiento al usar la semántica de
acq_rel
en i7? ¿Y las otras arquitecturas?
Gracias por todas las respuestas.