costo de operación atómica

92

¿Cuál es el costo de la operación atómica (cualquiera de comparación e intercambio o adición / disminución atómica)? ¿Cuántos ciclos consume? ¿Pausará otros procesadores en SMP o NUMA, o bloqueará los accesos a la memoria? ¿Vaciará el búfer de reordenamiento en la CPU fuera de servicio?

¿Qué efectos habrá en la caché?

Estoy interesado en CPU modernas y populares: x86, x86_64, PowerPC, SPARC, Itanium.

osgx
fuente
@Jason S, Cualquiera. Una diferencia entre cas y atomic inc / dec es insignificante.
osgx
2
Las operaciones atómicas en un x86 se vuelven más lentas a medida que se coloca más contención en la dirección de memoria. Creo que, en general, son aproximadamente un orden de magnitud más lentos que la operación no bloqueada, pero claramente esto variará según la operación, la contención y las barreras de memoria utilizadas.
Stephen Nutt
hmmm. escribe parece ser atómico en x86. 'Comprensión del kernel de Linux' -> spin_unlock
osgx
Una escritura de 32 bits es atómica en Java, es decir, es portablemente atómica (pero no tiene semántica de barrera de memoria, por lo que a menudo esto no es suficiente para los punteros). Agregar 1 normalmente no es atómico, a menos que agregue el prefijo LOCK. Acerca del kernel de Linux, no es necesario mirar spin_unlock. Consulte, en las versiones actuales, arch / x86 / include / asm / atomic_32.h (solía ser include / asm-i386 / atomic.h).
Blaisorblade
@Blaisorblade, JAva no está aquí. ¿Cuál es el costo de las operaciones BLOQUEADAS?
osgx

Respuestas:

60

He buscado datos reales de los últimos días y no encontré nada. Sin embargo, hice una investigación que compara el costo de las operaciones atómicas con los costos de los fallos de caché.

El costo del prefijo x86 LOCK, (incluido lock cmpxchgpara CAS atómico), antes de PentiumPro (como se describe en el documento), es un acceso a la memoria (como una pérdida de caché), + detener las operaciones de memoria de otros procesadores, + cualquier disputa con otros procesadores tratando de BLOQUEAR el autobús. Sin embargo, desde PentiumPro, para la memoria caché de escritura diferida normal (toda la memoria con la que se ocupa una aplicación, a menos que hable directamente con el hardware), en lugar de bloquear todas las operaciones de memoria, solo se bloquea la línea de caché relevante (según el enlace en la respuesta de @ osgx ) .

es decir, el núcleo demora la respuesta a las solicitudes de participación MESI y RFO para la línea hasta después de la parte de almacenamiento de la lockoperación ed real . Esto se denomina "bloqueo de caché" y solo afecta a esa línea de caché. Otros núcleos pueden estar cargando / almacenando o incluso colocando otras líneas al mismo tiempo.


En realidad, el caso de CAS puede ser más complicado, como se explica en esta página , sin tiempos, pero con una descripción detallada de un ingeniero de confianza. (Al menos para el caso de uso normal en el que realiza una carga pura antes del CAS real).

Antes de entrar en demasiados detalles, diré que una operación BLOQUEADA cuesta una pérdida de caché + la posible contención con otro procesador en la misma línea de caché, mientras que CAS + la carga anterior (que casi siempre se requiere excepto en mutexes, donde siempre CAS 0 y 1) puede costar dos fallos de caché.

Explica que una carga + CAS en una sola ubicación en realidad puede costar dos fallas de caché, como Load-Linked / Store-Conditional (ver allí para el último). Su explicación se basa en el conocimiento del protocolo de coherencia de caché MESI . Utiliza 4 estados para una línea de caché: M (odificado), E (exclusivo), S (hared), I (no válido) (y por lo tanto se llama MESI), explicado a continuación cuando sea necesario. El escenario, explicado, es el siguiente:

  • LOAD provoca una falta de caché: la línea de caché relevante se carga desde la memoria en estado Compartido (es decir, otros procesadores aún pueden mantener esa línea de caché en la memoria; no se permiten cambios en este estado). Si la ubicación está en la memoria, se omite este error de caché. Coste posible: 1 caché perdida. (se omite si la línea de caché está en estado compartido, exclusivo o modificado, es decir, los datos están en la caché L1 de esta CPU).
  • el programa calcula los nuevos valores para almacenar,
  • y ejecuta una instrucción CAS atómica.
    • Tiene que evitar la modificación simultánea, por lo que debe eliminar copias de la línea de caché del caché de otras CPU, para mover la línea de caché al estado Exclusivo. Coste posible: 1 caché perdida. Esto no es necesario si ya es de propiedad exclusiva, es decir, en el estado Exclusivo o Modificado. En ambos estados, ninguna otra CPU mantiene la línea de caché, pero en el estado Exclusivo no se ha modificado (todavía).
    • Después de esta comunicación, la variable se modifica en la caché local de nuestra CPU, en cuyo punto es visible globalmente para todas las demás CPU (porque sus cachés son coherentes con las nuestras). Eventualmente se escribirá en la memoria principal de acuerdo con los algoritmos habituales.
    • Otros procesadores que intenten leer o modificar esa variable primero deberán obtener esa línea de caché en modo Compartido o Exclusivo, y para hacerlo se pondrán en contacto con este procesador y recibirán la versión actualizada de la línea de caché. Una operación BLOQUEADA, en cambio, solo puede costar una falta de caché (porque la línea de caché se solicitará directamente en el estado Exclusivo).

En todos los casos, una solicitud de línea de caché puede ser detenida por otros procesadores que ya están modificando los datos.

Blaisorblade
fuente
¿Por qué el cambio de estado en otros costos de cpus como falta de 1 caché?
osgx
1
Porque es comunicación fuera de la CPU y, por lo tanto, más lenta que acceder a la caché. Mientras que una falla de caché tiene que pasar de otras CPU de todos modos. En realidad, podría darse el caso de que hablar con otra CPU sea más rápido que hablar con la memoria, si se utiliza una interconexión directa, como AMD Hypertransport (desde hace mucho tiempo), o Intel QuickPath Interconnect de Intel, en los últimos procesadores Xeon. basado en Nehalem. De lo contrario, la comunicación con otras CPU se realiza en el mismo FSB que el de la memoria. Busque HyperTransport y Front Side Bus en Wikipedia para obtener más información.
Blaisorblade
Vaya, nunca pensé que el suyo fuera tan caro: una pérdida de caché puede ser de unos pocos miles de ciclos.
Lothar
2
De Verdad? La cifra a la que estoy acostumbrado es: cien ciclos para fallas de caché y miles de ciclos para cambios de contexto / privilegio (incluidas las llamadas al sistema).
Blaisorblade
1
La pérdida de caché no es de unos pocos miles de ciclos. Son alrededor de 100ns, que normalmente son 300-350 ciclos de CPU ....
user997112
37

Hice algunos perfiles con la siguiente configuración: Se arrancó la máquina de prueba (AMD Athlon64 x2 3800+), se cambió al modo largo (interrupciones desactivadas) y la instrucción de interés se ejecutó en un bucle, se desenrollaron 100 iteraciones y 1000 ciclos de bucle. El cuerpo del bucle se alineó con 16 bytes. El tiempo se midió con una instrucción rdtsc antes y después del ciclo. Además, se ejecutó un bucle ficticio sin ninguna instrucción (que midió 2 ciclos por iteración de bucle y 14 ciclos para el resto) y el resultado se restó del resultado del tiempo de perfilado de la instrucción.

Se midieron las siguientes instrucciones:

  • " lock cmpxchg [rsp - 8], rdx" (tanto con coincidencia de comparación como con discrepancia),
  • " lock xadd [rsp - 8], rdx",
  • " lock bts qword ptr [rsp - 8], 1"

En todos los casos, el tiempo medido fue de aproximadamente 310 ciclos, el error fue de aproximadamente +/- 8 ciclos

Este es el valor para la ejecución repetida en la misma memoria (en caché). Con una falta de caché adicional, los tiempos son considerablemente más altos. Además, esto se hizo con solo uno de los 2 núcleos activos, por lo que el caché era de propiedad exclusiva y no se requirió sincronización de caché.

Para evaluar el costo de una instrucción bloqueada en un error de caché, agregué una wbinvldinstrucción antes de la instrucción bloqueada y puse el wbinvldmás add [rsp - 8], raxen el ciclo de comparación. En ambos casos, el costo fue de aproximadamente 80.000 ciclos por par de instrucciones. En caso de bloqueo bts, la diferencia de tiempo fue de aproximadamente 180 ciclos por instrucción.

Tenga en cuenta que este es el rendimiento recíproco, pero dado que las operaciones bloqueadas son operaciones de serialización, probablemente no haya diferencia en la latencia.

Conclusión: una operación bloqueada es pesada, pero una falta de caché puede ser mucho más pesada. Además: una operación bloqueada no provoca pérdidas de caché. Solo puede causar tráfico de sincronización de caché, cuando una línea de caché no es de propiedad exclusiva.

Para arrancar la máquina, utilicé una versión x64 de FreeLdr del proyecto ReactOS. Aquí está el código fuente de ASM:

#define LOOP_COUNT 1000
#define UNROLLED_COUNT 100

PUBLIC ProfileDummy
ProfileDummy:

    cli

    // Get current TSC value into r8
    rdtsc
    mov r8, rdx
    shl r8, 32
    or r8, rax

    mov rcx, LOOP_COUNT
    jmp looper1

.align 16
looper1:

REPEAT UNROLLED_COUNT
    // nothing, or add something to compare against
ENDR

    dec rcx
    jnz looper1

    // Put new TSC minus old TSC into rax
    rdtsc
    shl rdx, 32
    or rax, rdx
    sub rax, r8

    ret

PUBLIC ProfileFunction
ProfileFunction:

    cli

    rdtsc
    mov r8, rdx
    shl r8, 32
    or r8, rax
    mov rcx, LOOP_COUNT

    jmp looper2

.align 16
looper2:

REPEAT UNROLLED_COUNT
    // Put here the code you want to profile
    // make sure it doesn't mess up non-volatiles or r8
    lock bts qword ptr [rsp - 8], 1
ENDR

    dec rcx
    jnz looper2

    rdtsc
    shl rdx, 32
    or rax, rdx
    sub rax, r8

    ret
Timo
fuente
¡Gracias! ¿Puede publicar su código de prueba o probar Core2 / Core i3 / i5 / i7 usted mismo? ¿Se inicializaron todos los núcleos en su configuración de prueba?
osgx
Agregué el código fuente. Solo se inicializó un núcleo. Me encantaría ver los resultados de otras máquinas.
Timo
CLFLUSH debería ser una forma mucho más sencilla de vaciar una línea de caché que WBINVD de toda la caché. WBINVD también vaciará las cachés de instrucciones, lo que provocará pérdidas de caché adicionales.
Peter Cordes
Quizás sea interesante probar el caso de que la línea de caché esté activa en estado compartido. Puede hacer que eso suceda haciendo que otro hilo lo lea con una carga pura.
Peter Cordes
4

En SMP basado en bus, el prefijo atómico LOCKafirma (enciende) una señal de cable de bus LOCK#. Prohibirá otros cpus / dispositivos en el bus para usarlo.

Libro Ppro & P2 http://books.google.com/books?id=3gDmyIYvFH4C&pg=PA245&dq=lock+instruction+pentium&lr=&ei=_E61S5ehLI78zQSzrqwI&cd=1#v=onepage&q=lock%20instruction%20pentium&q=false pages

Las instrucciones bloqueadas se serializan, sincronizan operaciones de .... / about Fuera de servicio / RMW bloqueado / lectura-modificación-escritura = atómica en sí / instrucción asegura que el procesador ejecutará todas las instrucciones antes de la instrucción bloqueada antes de ejecutarla. / about yet not flushed write / obliga a que todas las escrituras publicadas dentro del procesador se descarguen en la memoria externa antes de ejecutar la siguiente instrucción.

/ about SMP / el semáforo está en caché en estado S ... emitiendo una transacción de lectura e invalidación para 0 bytes de fecha (esto es una eliminación / de copias compartidas de la línea de caché en CPU adyacentes /)

osgx
fuente