¿Por qué std :: atomic <T> :: is_lock_free () no es estático ni constexpr?

9

¿Alguien puede decirme si std :: atomic :: is_lock_free () no es estático y constexpr? Tenerlo no estático y / o como no constexpr no tiene sentido para mí.

Bonita Montero
fuente
3
¿Estás al tanto is_always_lock_free?
Mike van Dyke
3
Voy a lanzar "alineación" por ahí.
Max Langhof
@MaxLanghof ¿Quiere decir que no todas las instancias se alinearán de la misma manera?
curioso
1
Mike, no, no estaba al tanto, pero gracias por esta pista; Es de mucha ayuda para mi. Pero me pregunto por qué hay una decisión entre is_lock_free () y is_always_lock_free. No puede ser debido a atómicos no alineados, como sugirieron otros aquí, ya que el lenguaje define accesos no alineados para tener un comportamiento indefinido de todos modos.
Bonita Montero

Respuestas:

10

Como se explica en cppreference :

Todos los tipos atómicos, excepto std :: atomic_flag, pueden implementarse utilizando mutexes u otras operaciones de bloqueo, en lugar de utilizar las instrucciones de la CPU atómica sin bloqueo. A veces, los tipos atómicos también pueden estar libres de bloqueo, por ejemplo, si solo los accesos de memoria alineados son naturalmente atómicos en una arquitectura dada, los objetos desalineados del mismo tipo tienen que usar bloqueos.

El estándar C ++ recomienda (pero no requiere) que las operaciones atómicas sin bloqueo también estén libres de dirección, es decir, adecuadas para la comunicación entre procesos que utilizan memoria compartida.

Como lo mencionaron muchos otros, std::is_always_lock_freepodría ser lo que realmente estás buscando.


Editar: para aclarar, los tipos de objetos C ++ tienen un valor de alineación que restringe las direcciones de sus instancias a solo ciertos múltiplos de potencias de dos ( [basic.align]). Estos valores de alineación están definidos por la implementación para los tipos fundamentales y no necesitan ser iguales al tamaño del tipo. También pueden ser más estrictos de lo que el hardware podría soportar.

Por ejemplo, x86 (principalmente) admite accesos no alineados. Sin embargo, encontrará que la mayoría de los compiladores tienen alignof(double) == sizeof(double) == 8para x86, ya que los accesos no alineados tienen una serie de desventajas (velocidad, almacenamiento en caché, atomicidad ...). Pero, por ejemplo, #pragma pack(1) struct X { char a; double b; };o le alignas(1) double x;permite tener "no alineados" double. Entonces, cuando cppreference habla de "accesos de memoria alineados", presumiblemente lo hace en términos de la alineación natural del tipo para el hardware, no utilizando un tipo C ++ de una manera que contradiga sus requisitos de alineación (que sería UB).

Aquí hay más información: ¿Cuál es el efecto real de los accesos no alineados exitosos en x86?

¡Por favor también mire los comentarios perspicaces de @Peter Cordes a continuación!

Max Langhof
fuente
1
3286 x86 es un buen ejemplo de dónde encuentra ABIs alignof(double)==4. Pero std::atomic<double>todavía tiene en alignof() = 8lugar de verificar la alineación en tiempo de ejecución. El uso de una estructura empaquetada que subalinea atómica rompe el ABI y no es compatible. (GCC para x86 de 32 bits prefiere dar una alineación natural a los objetos de 8 bytes, pero las reglas de empaquetamiento de estructuras anulan eso y solo se basan en alignof(T), por ejemplo, en el Sistema i386 V. G ++ solía tener un error donde atomic<int64_t>dentro de una estructura podría no ser atómico porque simplemente asumió. ¡GCC (para C no C ++) todavía tiene este error!)
Peter Cordes
2
Pero una implementación correcta de C ++ 20 std::atomic_ref<double>rechazará por doublecompleto la falta de alineación, o verificará la alineación en tiempo de ejecución en plataformas donde es legal por simple doubley int64_testar menos alineado de forma natural. (Debido a que atomic_ref<T>opera en un objeto que se declaró como plano T, y solo tiene una alineación mínima alignof(T)sin la oportunidad de darle una alineación adicional.)
Peter Cordes
2
Consulte gcc.gnu.org/bugzilla/show_bug.cgi?id=62259 para el error libstdc ++ ahora corregido, y gcc.gnu.org/bugzilla/show_bug.cgi?id=65146 para el error C aún roto, incluido un caso de prueba ISO C11 puro que muestra el desgarro de un _Atomic int64_tcuando se compila con la corriente gcc -m32. De todos modos, mi punto es que reales compiladores no son compatibles con las atómicas bajo-alineados, y no hacen los controles de tiempo de ejecución (¿todavía?), Así que #pragma packo __attribute__((packed))se acaba de conducir a la no atomicidad; los objetos aún informarán que lo son lock_free.
Peter Cordes
1
Pero sí, el propósito de is_lock_free()es permitir que las implementaciones funcionen de manera diferente a como funcionan las actuales; con comprobaciones de tiempo de ejecución basadas en la alineación real para usar instrucciones atómicas compatibles con HW o para usar un bloqueo.
Peter Cordes
3

Puedes utilizar std::is_always_lock_free

is_lock_free depende del sistema real y no se puede determinar en tiempo de compilación.

Explicación relevante:

Los tipos atómicos también pueden estar libres de bloqueo, por ejemplo, si solo los accesos de memoria alineados son naturalmente atómicos en una arquitectura dada, los objetos desalineados del mismo tipo tienen que usar bloqueos.

Darune
fuente
1
std::numeric_limits<int>::maxdepende de la arquitectura, pero es estática y constexpr. Supongo que no hay nada malo en la respuesta, pero no compro la primera parte del razonamiento
idclev 463035818
1
¿No define el lenguaje que los accesos no alineados tienen un comportamiento indefinido de todos modos para que una evaluación de bloqueo libre o no en tiempo de ejecución no tenga sentido?
Bonita Montero el
1
No tiene sentido decidir entre accesos alineados y no alineados, ya que el lenguaje define este último como un comportamiento indefinido.
Bonita Montero el
@BonitaMontero Hay un sentido "sin alinear en la alineación de objetos C ++" y "sin alinear en lo que le gusta al hardware". Esos no son necesariamente los mismos, pero en la práctica lo son con frecuencia. El ejemplo que muestra es uno de esos casos en el que el compilador aparentemente tiene la suposición incorporada de que los dos son iguales, lo que solo significa que eso no is_lock_freetiene sentido en ese compilador .
Max Langhof
1
Puede estar bastante seguro de que un atómico tendría una alineación adecuada si existe un requisito de alineación.
Bonita Montero el
1

Instalé Visual Studio 2019 en mi PC con Windows y este dispositivo también tiene un compilador ARMv8. ARMv8 permite accesos no alineados, pero la comparación y los intercambios, las adiciones bloqueadas, etc. están obligados a alinearse. Y también la carga pura / almacenamiento puro usando ldpo stp(pares de carga o pares de almacenamiento de registros de 32 bits) solo se garantiza que sean atómicos cuando están naturalmente alineados.

Entonces escribí un pequeño programa para verificar qué devuelve is_lock_free () para un puntero atómico arbitrario. Así que aquí está el código:

#include <atomic>
#include <cstddef>

using namespace std;

bool isLockFreeAtomic( atomic<uint64_t> *a64 )
{
    return a64->is_lock_free();
}

Y este es el desmontaje de isLockFreeAtomic

|?isLockFreeAtomic@@YA_NPAU?$atomic@_K@std@@@Z| PROC
    movs        r0,#1
    bx          lr
ENDP

Esto es solo returns true, alias 1.

Esta implementación elige usar alignof( atomic<int64_t> ) == 8para que todos atomic<int64_t>estén correctamente alineados. Esto evita la necesidad de verificaciones de alineación de tiempo de ejecución en cada carga y almacén.

(Nota del editor: esto es común; la mayoría de las implementaciones de C ++ en la vida real funcionan de esta manera. Por eso std::is_always_lock_freees tan útil: porque generalmente es cierto para tipos donde is_lock_free()siempre es cierto).

Bonita Montero
fuente
1
Sí, la mayoría de las implementaciones optar por dar atomic<uint64_t>y alignof() == 8para que no tenga que comprobar el alineamiento en tiempo de ejecución. Esta antigua API les da la opción de no hacerlo, pero en HW actual tiene mucho más sentido solo requerir alineación (de lo contrario, UB, por ejemplo, no atomicidad). Incluso en el código de 32 bits donde int64_tsolo podría tener una alineación de 4 bytes, atomic<int64_t>requiere 8 bytes. Vea mis comentarios sobre otra respuesta
Peter Cordes el
Poner en palabras diferentes: Si A elige compilador para hacer alignofvalor para un tipo fundamental de la misma como la "buena" la alineación del hardware, entonces is_lock_free será siempre true(y lo hará is_always_lock_free). Su compilador aquí hace exactamente esto. Pero la API existe para que otros compiladores puedan hacer cosas diferentes.
Max Langhof
1
Puede estar bastante seguro de que si el lenguaje dice que el acceso no alineado tiene un comportamiento indefinido, todos los atómicos deben estar correctamente alineados. Ninguna implementación hará ninguna verificación de tiempo de ejecución debido a eso.
Bonita Montero el
@BonitaMontero Sí, pero no hay nada en el lenguaje que prohíba alignof(std::atomic<double>) == 1(por lo que no habría "acceso no alineado" en el sentido de C ++, por lo tanto, no UB), incluso si el hardware solo puede garantizar operaciones atómicas sin bloqueo para doubles en 4 o Límites de 8 bytes. El compilador tendría que usar bloqueos en los casos no alineados (y devolver el valor booleano apropiado de is_lock_free, dependiendo de la ubicación de la memoria de la instancia del objeto).
Max Langhof