2009-08-18 14 views
9

En Java, la actualización de las variables dobles y largas puede no ser atómica, ya que las dobles/largas se tratan como dos variables separadas de 32 bits.Está Actualizando la operación doble atomic

http://java.sun.com/docs/books/jls/second_edition/html/memory.doc.html#28733

En C++, si estoy usando el procesador Intel de 32 bits + compilador Microsoft Visual C++, está actualizando doble (8 bytes) operación atómica?

No encuentro mucha mención de especificación sobre este comportamiento.

Cuando digo "variable atómica", esto es lo que quiero decir:

Tema Una tratando de escribir 1 a la variable x. El hilo B intenta escribir 2 en la variable x.

Obtendremos el valor 1 o 2 de la variable x, pero no un valor indefinido.

+0

Sí, 32-bit x86 (desde Pentium original) tiene [soporte de hardware eficiente] (https://stackoverflow.com/questions/36624881/why-is-integer-assignment-on-a-naturally-aligned-variable -atomic) para 'std :: atomic at- 'sin bloqueo cargar, almacenar y CAS. Si su compilador crea un código eficiente o no es otro problema: https://stackoverflow.com/questions/45055402/atomic-double-floating-point-or-sse-avx-vector-load-store-on-x86-64. El 'doble' alineado nunca tendrá" rasgado ", pero es más seguro usar' std :: atomic '. –

Respuesta

8

Esto es específico del hardware y depende de la arquitectura. Para x86 y x86_64, se garantiza que las escrituras o lecturas de 8 bytes serán atómicas, si están alineadas. Citando de la Memoria Arquitectura Intel pedidos Libro Blanco:

Intel 64 de memoria pedir garantías que para cada uno de los siguientes instrucciones de memoria de acceso, aparece la operación de la memoria constituyente ejecutar como un único acceso a la memoria independientemente del tipo de memoria:

  1. Instrucciones que leen o escriben un solo byte.

  2. Instrucciones que leen o escriben una palabra (2 bytes) cuya dirección es alineada en un límite de 2 bytes.

  3. Instrucciones que leen o escriben una doble palabra (4 bytes) cuya dirección es alineada en un límite de 4 bytes.

  4. Instrucciones que leen o escriben un quadword (8 bytes) cuya dirección es alineada en un límite de 8 bytes.

Todas las instrucciones bloqueadas (la instrucción xchg implícitamente bloqueado y otros instrucciones de lectura-modificación-escritura con un prefijo bloqueo) son una indivisibles e secuencia ininterrumpida de la carga (s) seguido de tienda (s) independientemente de tipo de memoria y alineación.

+3

También depende del compilador, que no es necesario para garantizar que los dobles estén alineados en 8 en primer lugar, o para usar un solo quadword op para leerlos o escribirlos. Aunque pensaría que probablemente sea así, y también espero que los documentos visuales de C++ sí lo hagan o no. –

+1

Sí, esto está especificado en los compiladores ABI. Para las variables no automáticas, los dobles siempre están alineados, excepto si se especifica explícitamente como no alineado. Para las variables en la pila en un sistema de 32 bits pueden desalinearse si la pila se desalinea de algún modo, por ejemplo, se llama a una función desde un programa externo, no C. Pero no desea devolver objetos de la pila en una función de todos modos ... – hirschhornsalz

+0

No devuelve los automáticos, pero puede pasarles un puntero en una función que llame. Pero supongo que lo que has dicho es suficiente para el que pregunta: siempre que controle cómo se definieron, puede asegurarse de que el acceso a sus dobles sea atómico. –

-2

No creo que en ninguna arquitectura, el cambio de subprocesos/contexto interrumpiría la actualización de un registro hasta la mitad, por lo que se quedarán con por ejemplo 18 bits actualizados de los 32 bits que iba a actualizar. Lo mismo para actualizar una ubicación de memoria (a condición de que sea una unidad de acceso básico, 8,16,32,64 bits, etc.).

+1

El problema no es el cambio de contexto, es multinúcleo y multicpu. – AProgrammer

+0

Incluso en la arquitectura multinúcleo/multi CPU, el controlador de memoria debe serializar el acceso a la memoria. Es eléctricamente imposible permitir que múltiples dispositivos accedan al mismo circuito al mismo tiempo. El controlador de memoria accede a la memoria en bloques, y en unidades enteras de ancho de bus de datos, por lo tanto, no es posible tener una actualización parcial de una ubicación de memoria. – Indy9000

+1

Entonces, ¿qué pasa si el doble se encuentra a través del límite de dos líneas de caché? Dudo que MSVC++ lo haga, porque todo estará alineado con su tamaño en potencias de 2. Pero si está generalizando, no es un requisito del estándar de C++ (y en al menos uno de los ABI de ARM, anhela y duplica solo tienen que estar alineados en 4, no alineados en 8). –

-2

¿Ha recibido esta pregunta?Me encontré con un programa de prueba simple de cambiar un doble:

 
#include <stdio.h> 

int main(int argc, char** argv) 
{ 
    double i = 3.14159265358979323; 
    i += 84626.433; 
} 

I compilado sin optimizaciones (-O0 gcc), y todas las operaciones de asignación se llevan a cabo con las instrucciones individuales de ensamblador como fldl .LC0 y faddp %st, %st(1). (i += 84626.433 está, por supuesto, hecho dos operaciones, faddp y fstpl).

¿Se puede interrumpir realmente un hilo dentro de una sola instrucción como faddp?

+0

"¿Puede realmente interrumpirse un hilo dentro de una sola instrucción como faddp?". No, el hilo no puede ser "interrumpido" dentro de una única instrucción, dos CPU pueden realizar sus instrucciones al mismo tiempo, y una CPU puede ver solo la parte del resultado de la segunda CPU si la instrucción no se realiza en un solo transacción de autobús. – Suma

1

Es seguro asumir que la actualización de un doble nunca es atómica, incluso si su tamaño es el mismo que un int con garantía atómica. La razón es que si tiene una ruta de procesamiento diferente ya que es un tipo de datos no crítico y costoso. Por ejemplo, incluso las barreras de datos generalmente mencionan que no se aplican a datos/operaciones de punto flotante en general. Visual C++ alineará tipos primitivos (ver article) y mientras eso debería garantizar que sus bits no se confunden al escribir en la memoria (la alineación de 8 bytes siempre está en una línea de caché de 64 o 128 bits) el resto depende de cómo la CPU maneja datos no atómicos en su caché y si la lectura/vaciado de una línea de caché es interrumpible. Por lo tanto, si busca en los documentos de Intel el tipo de núcleo que está utilizando y le da esa garantía, estará en condiciones de hacerlo.

La razón por la cual la especificación Java es tan conservadora es que se supone que debe ejecutarse de la misma manera en una antigua 386 y en Corei7. Lo cual es por supuesto delirante pero una promesa es una promesa, por lo tanto, promete menos :-)

La razón por la que estoy diciendo que debes buscar el doc CPU es que tu CPU podría ser una antigua 386 o similar: -)) No olvide que en una CPU de 32 bits, su bloque de 8 bytes necesita 2 "rondas" para acceder, por lo que está a merced de la mecánica del acceso a la memoria caché.

El enjuague de la línea de caché proporciona una garantía de coherencia de datos mucho mayor solo para una CPU razonablemente reciente con garantía de Intel (consistencia automática de caché).

+0

Ser una CPU x86 de 32 bits no significa que todas las rutas de datos internas son solo de 32 bits. Por ejemplo, Pentium 4 (incluso P4 inicial de 32 bits solamente) realiza una carga alineada de 16 bytes 'movaps' en un único acceso a su caché L1D. Señala que el acceso atómico al caché no garantiza la atomicidad en general (p. Ej., AMD K10 tiene carga/almacenamiento atómico de 16B SSE en un solo socket, pero el protocolo de coherencia introduce [desgarre en límites de 8B para hilos en diferentes enchufes] (https : //stackoverflow.com/questions/7646018/sse-instructions-which-cpus-can-do-atomic-16b-memory-operations/7647825#7647825)) –

+0

Algunos de los primeros 386 sistemas podrían tener solo un 16 -bit bus de datos (y no caché interna), por lo que significaría que un 'doble' tomó 4 ciclos de memoria. De todos modos, dado que el MSVC++ moderno no va a hacer código que funcione con algo más antiguo que un Pentium (P5), se le garantiza que las cargas/tiendas alineadas de 8B son atómicas, incluso si se hace con x87 o SSE. (No tengo idea de por qué dices que FP está menos optimizado. X86 ha tenido puntos flotantes de alto rendimiento durante años). Una vez que los datos llegan a la memoria caché, no recuerdan cómo llegaron allí, por lo que "datos no atómicos en la memoria caché" es extraño. –

+0

Es * seguro * suponer que actualizar un 'doble' nunca es atómico, sino demasiado conservador. –

-2

En multinúcleo, además de ser atómico, debe preocuparse por la coherencia del caché, de modo que la lectura del hilo vea el nuevo valor en su caché cuando el escritor se haya actualizado.

+0

La atomicidad y la coherencia del caché son dos cosas diferentes. La atomicidad está relacionada con que usted ve el valor anterior o el nuevo, nunca un estado transitorio que podría ser necesario, la coherencia del caché está relacionada con el orden en que ve la modificación de varias ubicaciones de memoria. – AProgrammer

+0

x86 tiene cachés coherentes. No tienes que preocuparte por eso. Siempre que el lector realmente recargue de la memoria, en lugar de reutilizar un valor que el compilador puede guardar en un registro, verá la actualización con el tiempo. (Esta es la razón por la que debe usar 'std :: atomic ' ahora que C++ 11 existe. Aunque los compiladores actuales crean un código ineficiente para él: https://stackoverflow.com/questions/45055402/atomic-double-floating-point -or-sse-avx-vector-load-store-on-x86-64) –

+0

@AProgrammer: coherencia significa que dos cachés no pueden tener valores diferentes para una línea de caché, por lo que una vez que una tienda se ha comprometido con la caché L1D en uno CPU, ninguna otra CPU puede cargar un valor diferente. https://en.wikipedia.org/wiki/MESI_protocol. Ordenar entre modificaciones a diferentes líneas de caché es otro nivel de funcionalidad construido sobre cachés coherentes. –

Cuestiones relacionadas