stringtranslate.com

Compartir falsamente

En informática , el uso compartido falso es un patrón de uso que degrada el rendimiento y que puede surgir en sistemas con cachés distribuidos y coherentes del tamaño del bloque de recursos más pequeño administrado por el mecanismo de almacenamiento en caché. Cuando un participante del sistema intenta acceder periódicamente a datos que no están siendo alterados por otra parte, pero esos datos comparten un bloque de caché con datos que sí están siendo alterados, el protocolo de almacenamiento en caché puede obligar al primer participante a recargar todo el bloque de caché a pesar de la falta de necesidad lógica. [1] El sistema de almacenamiento en caché no es consciente de la actividad dentro de este bloque y obliga al primer participante a soportar la sobrecarga del sistema de almacenamiento en caché requerida por el verdadero acceso compartido de un recurso.

Cachés de CPU multiprocesador

El uso más común de este término es en los cachés de CPU multiprocesador modernos , donde la memoria se almacena en caché en líneas de una pequeña potencia de dos palabras de tamaño (por ejemplo, 64 bytes alineados y contiguos ). Si dos procesadores operan con datos independientes en la misma región de dirección de memoria almacenable en una sola línea, los mecanismos de coherencia de caché en el sistema pueden forzar a toda la línea a través del bus o interconectarse con cada escritura de datos, lo que obliga a bloqueos de memoria además de desperdiciar ancho de banda del sistema . En algunos casos, la eliminación del uso compartido falso puede dar como resultado mejoras de rendimiento de orden de magnitud. [2] El uso compartido falso es un artefacto inherente de los protocolos de caché sincronizados automáticamente y también puede existir en entornos como sistemas de archivos distribuidos o bases de datos, pero la prevalencia actual se limita a los cachés de RAM.

Ejemplo

#include <iostream> #include <hilo> #include <nuevo> #include <atómico> #include <crono> #include <pestillo> #include <vector>       utilizando el espacio de nombres std ; utilizando el espacio de nombres chrono ;    #ifdefined(__cpp_lib_hardware_interference_size) // tamaño de línea de caché predeterminado desde el tiempo de ejecución constexpr size_t CL_SIZE = hardware_constructive_interference_size ; #else // tamaño de línea de caché más común en caso contrario constexpr size_t CL_SIZE = 64 ; #endif        int main () { vector < jthread > threads ; int hc = jthread :: hardware_concurrency (); hc = hc <= CL_SIZE ? hc : CL_SIZE ; for ( int nThreads = 1 ; nThreads <= hc ; ++ nThreads ) { // sincronizar el comienzo de los subprocesos de forma aproximada en el nivel del núcleo latch coarseSync ( nThreads ); // sincronización fina mediante atomic en el espacio de usuario atomic_uint fineSync ( nThreads ); // tantos caracteres como quepan en una línea de caché struct alignas ( CL_SIZE ) { char shareds [ CL_SIZE ]; } cacheLine ; // suma de los tiempos de ejecución de todos los subprocesos atomic_int64_t nsSum ( 0 ); for ( int t = 0 ; t != nThreads ; ++ t ) subprocesos . emplace_back ( [ & ]( char volátil & c ) { coarseSync . arrive_and_wait (); // sincronizar el inicio de la ejecución del hilo en el nivel del núcleo if ( fineSync . fetch_sub ( 1 , memory_order :: relajado ) != 1 ) // sincronización fina en el nivel del usuario while ( fineSync . load ( memory_order :: relajado ) ); inicio automático = reloj_de_alta_resolución :: ahora (); for ( tamaño_t r = 10'000'000 ; r -- ; ) c = c + 1 ; nsSum += duración_cast < nanosegundos > (                                                                                                       reloj_de_alta_resolución :: now () - start ) .count ( ); }, ref ( cacheLine.shareds [ t ] ) ) ; threads.resize ( 0 ); // unir todos los hilos cout << nThreads << " : " << ( int ) ( nsSum /(1.0e7 * nThreads ) + 0.5 ) << endl ; } }                            

Este código muestra el efecto de la compartición falsa. Crea un número creciente de subprocesos desde un subproceso hasta el número de subprocesos físicos en el sistema. Cada subproceso incrementa secuencialmente un byte de una línea de caché, que en su conjunto se comparte entre todos los subprocesos. Cuanto mayor sea el nivel de contención entre subprocesos, más tiempo llevará cada incremento. Estos son los resultados en un sistema Zen4 con 16 núcleos y 32 subprocesos:

1: 12: 43:64:95:116:137:158:179:1610:1811:2112:2513:2914:3515:3916:4117:4318:4419:4820:4921:5122:5323:5824:6125:6826:7527:7928:8229:8530:8831:9132:94

Como puede ver, en el sistema en cuestión puede tomar hasta 100 nanosegundos completar una operación de incremento en la línea de caché compartida, lo que corresponde a aproximadamente 420 ciclos de reloj en esta CPU.

Mitigación

Existen formas de mitigar los efectos de la compartición falsa. Por ejemplo, la compartición falsa en cachés de CPU se puede prevenir reordenando las variables o agregando relleno (bytes no utilizados) entre las variables. Sin embargo, algunos de estos cambios de programa pueden aumentar el tamaño de los objetos, lo que lleva a un mayor uso de memoria. [2] Las transformaciones de datos en tiempo de compilación también pueden mitigar la compartición falsa. [3] Sin embargo, algunas de estas transformaciones pueden no estar siempre permitidas. Por ejemplo, el borrador estándar del lenguaje de programación C++ de C++23 exige que los miembros de datos se distribuyan de manera que los miembros posteriores tengan direcciones más altas. [4]

Existen herramientas para detectar el uso compartido falso. [5] [6] También existen sistemas que detectan y reparan el uso compartido falso en la ejecución de programas. Sin embargo, estos sistemas generan cierta sobrecarga de ejecución. [7] [8]

Referencias

  1. ^ Patterson, David (2012). Organización y diseño de computadoras: la interfaz hardware/software . Waltham, MA: Morgan Kaufmann. p. 537. ISBN 978-0-12-374750-1.OCLC 746618653  .
  2. ^ ab Bolosky, William J.; Scott, Michael L. (22 de septiembre de 1993). "False sharing and its effect on shared memory performance" (Compartir falsamente y su efecto en el rendimiento de la memoria compartida). Sedms'93: USENIX Systems en USENIX Experiences with Distributed and Multiprocessor Systems (Experiencias de USENIX con sistemas distribuidos y multiprocesador) . 4. Consultado el 11 de julio de 2021 .
  3. ^ Jeremiassen, Tor E.; Eggers, Susan J. (1995). "Reducción de la compartición falsa en multiprocesadores de memoria compartida mediante transformaciones de datos en tiempo de compilación". Avisos SIGPLAN de la ACM . 30 (8). Association for Computing Machinery (ACM): 179–188. doi : 10.1145/209937.209955 . ISSN  0362-1340.
  4. ^ "Borrador de trabajo, estándar para el lenguaje de programación C++ [clase]". eel.is . Consultado el 11 de julio de 2021 .
  5. ^ "perf-c2c(1)". Página del manual de Linux . 2016-09-01 . Consultado el 2021-08-08 .
  6. ^ Chabbi, Milind; Wen, Shasha; Liu, Xu (10 de febrero de 2018). "Detección de falsos usos compartidos al vuelo con Featherlight". Actas del 23.º Simposio SIGPLAN de la ACM sobre principios y prácticas de programación paralela . Nueva York, NY, EE. UU.: ACM. págs. 152–167. doi :10.1145/3178487.3178499. ISBN . 9781450349826.
  7. ^ Nanavati, Mihir; Spear, Mark; Taylor, Nathan; Rajagopalan, Shriram; Meyer, Dutch T.; Aiello, William; Warfield, Andrew (2013). "¿De quién es la línea de caché?". Actas de la 8.ª Conferencia Europea de la ACM sobre sistemas informáticos . Nueva York, Nueva York, EE. UU.: ACM Press. págs. 141–154. doi :10.1145/2465351.2465366. ISBN . 9781450319942.
  8. ^ Liu, Tongping; Berger, Emery D. (18 de octubre de 2011). "SHERIFF: detección precisa y mitigación automática de compartición falsa". Avisos SIGPLAN de la ACM . 46 (10). Association for Computing Machinery (ACM): 3–18. doi :10.1145/2076021.2048070. ISSN  0362-1340.

Enlaces externos