CUDA: ¿Cuándo usar la memoria compartida y cuándo confiar en el almacenamiento en caché L1?

Después del lanzamiento de Compute Capability 2.0 (Fermi), me he preguntado si queda algún caso de uso para la memoria compartida. Es decir, ¿cuándo es mejor usar la memoria compartida que simplemente dejar que L1 haga su magia en segundo plano?

¿La memoria compartida simplemente está ahí para permitir que los algoritmos diseñados para CC < 2.0 se ejecuten de manera eficiente sin modificaciones?

Para colaborar a través de la memoria compartida, los subprocesos en un bloque escriben en la memoria compartida y se sincronizan con __syncthreads(). ¿Por qué no simplemente escribir en la memoria global (a través de L1) y sincronizar con __threadfence_block()? La última opción debería ser más fácil de implementar, ya que no tiene que relacionarse con dos ubicaciones diferentes de valores, y debería ser más rápida porque no hay una copia explícita de la memoria global a la compartida. Dado que los datos se almacenan en caché en L1, los subprocesos no tienen que esperar a que los datos lleguen a la memoria global.

Con la memoria compartida, se garantiza que un valor que se puso allí permanece allí durante la duración del bloque. Esto es lo opuesto a los valores en L1, que se desalojan si no se usan con la suficiente frecuencia. ¿Hay algún caso en el que sea mejor almacenar en caché esos datos que se usan con poca frecuencia en la memoria compartida que dejar que L1 los administre en función del patrón de uso que el algoritmo realmente tiene?

preguntado el 30 de junio de 12 a las 17:06

3 Respuestas

2 grandes razones por las que el almacenamiento en caché automático es menos eficiente que la memoria de borrador manual (también se aplica a las CPU)

  1. los accesos paralelos a direcciones aleatorias son más eficientes. Ejemplo: histograma. Digamos que desea incrementar N contenedores, y cada uno tiene una separación de > 256 bytes. Luego, debido a las reglas de combinación, eso dará como resultado N lecturas/escrituras en serie, ya que la memoria caché y global están organizadas en grandes bloques de ~256 bytes. La memoria compartida no tiene ese problema.

Además, para acceder a la memoria global, debe realizar la traducción de direcciones virtuales a físicas. Tener un TLB que pueda hacer muchas traducciones en || será bastante caro. No he visto ninguna arquitectura SIMD que realmente haga cargas/almacenes vectoriales en || y creo que esta es la razón por la cual.

  1. evita volver a escribir valores muertos en la memoria, lo que desperdicia ancho de banda y energía. Ejemplo: en una canalización de procesamiento de imágenes, no desea que sus imágenes intermedias se descarguen en la memoria.

Asimismo, según un empleado de NVIDIA, los cachés L1 actuales son de escritura simultánea (escriben inmediatamente en el caché L2), lo que ralentizará su programa.

Básicamente, los cachés se interponen si realmente quieres rendimiento.

Respondido el 17 de Septiembre de 18 a las 22:09

Compute Capability 2.* y 3.* invalidan la línea de caché L1 al escribir. La capacidad de cómputo 3.0-3.5 no almacena en caché las lecturas globales en L1. En los dispositivos con capacidad de cómputo 3.*, el ancho de banda de la memoria compartida con 8 bytes por banco es en realidad de 256 bytes/clk, mientras que L1 está limitado a 128 bytes desde una línea de caché. Como lo indica Yale, la memoria compartida tiene conflictos bancarios (todo el acceso debe ser a diferentes bancos o a la misma dirección en un banco), mientras que L1 tiene divergencia de direcciones (todas las direcciones deben estar en la misma línea de caché de 128 bytes), por lo que la memoria compartida es mucho más eficiente en acceso aleatorio. - Greg Smith

Permítanme ofrecer una conjetura sobre por qué el acceso a la memoria SIMD es prácticamente inexistente en los procesadores de propósito general (por ejemplo, Intel AVX2 tiene un conjunto, pero en realidad es en serie). Estoy bastante convencido de que se debe al gran costo de hacer traducciones de direcciones virtuales a físicas, que el acceso a la memoria compartida no necesita porque es su propio espacio de direcciones. ¡Imagínese el costo de tener que hacer 32 búsquedas de TLB en paralelo! ¿Quizás hay una optimización si las 32 direcciones caen en la misma página? - yale zhang

Hasta donde yo sé, el caché L1 en una GPU se comporta de manera muy similar al caché en una CPU. Entonces, su comentario de que "Esto es opuesto a los valores en L1, que se desalojan si no se usan con la frecuencia suficiente" no tiene mucho sentido para mí.

Los datos en la memoria caché L1 no se desalojan cuando no se usan con la frecuencia suficiente. Por lo general, se desaloja cuando se realiza una solicitud para una región de memoria que no estaba previamente en caché y cuya dirección se resuelve en una que ya está en uso. No conozco el algoritmo de almacenamiento en caché exacto empleado por NVidia, pero asumiendo una asociativa de n vías regular, cada entrada de memoria solo se puede almacenar en caché en un pequeño subconjunto de la memoria caché completa, según su dirección

Supongo que esto también puede responder a tu pregunta. Con la memoria compartida, obtienes control total sobre qué se almacena dónde, mientras que con el caché, todo se hace automáticamente. Aunque el compilador y la GPU aún pueden ser muy inteligentes para optimizar los accesos a la memoria, a veces aún puede encontrar una mejor manera, ya que usted es quien sabe qué entrada se dará y qué subprocesos harán qué (hasta cierto punto). extensión, por supuesto)

Respondido 01 Jul 12, 04:07

Gracias, eso responde a mi pregunta. Había imaginado que el caché podía hacer un seguimiento de los elementos que se usaban más y prefería almacenarlos en caché. He leído sobre cachés asociativos de n vías ahora y me parece que el principal problema es que pueden arrojar un valor que se usa a menudo simplemente porque otra línea de caché encaja en esa ranura. - roger dahl

Creo que eso significa que una buena estrategia para escribir programas CUDA a menudo puede ser escribir primero el algoritmo para usar solo la memoria global y ver si L1 funciona lo suficientemente bien como para ocultar la latencia de la memoria. Y luego considere la optimización manual con memoria compartida si el algoritmo resulta estar limitado a la memoria. - roger dahl

El almacenamiento en caché de datos a través de varias capas de memoria siempre debe seguir un protocolo de coherencia de caché. Existen varios protocolos de este tipo y la decisión sobre cuál es el más adecuado es siempre una compensación.

Puedes echar un vistazo a algunos ejemplos:

Relacionado con las GPU

Generalmente para unidades de computación

No quiero entrar en muchos detalles, porque es un dominio enorme y no soy un experto. Lo que quiero señalar es que en un sistema de memoria compartida (aquí el término compartido no se refiere a la llamada memoria compartida de GPU) donde muchas unidades de cómputo (CU) necesitan datos al mismo tiempo, hay un protocolo de memoria que intenta mantener los datos cerca de las unidades para que puedan recuperarlos lo más rápido posible. En el ejemplo de una GPU, cuando muchos subprocesos en el mismo SM (multiprocesador simétrico) acceden a los mismos datos, debe haber una coherencia en el sentido de que si el subproceso 1 lee una porción de bytes de la memoria global y en el ciclo siguiente, el subproceso 2 es va a acceder a estos datos, entonces una implementación eficiente sería tal que el subproceso 2 es consciente de que los datos ya se encuentran en el caché L1 y pueden acceder a ellos rápidamente. Esto es lo que intenta lograr el protocolo de coherencia de caché, permitir que todas las unidades de cómputo estén actualizadas con los datos que existen en los cachés L1, L2, etc.

Sin embargo, mantener los subprocesos actualizados, o bien, mantener los subprocesos en estados coherentes, tiene un costo que esencialmente es la falta de ciclos.

En CUDA, al definir la memoria como compartida en lugar de caché L1, la libera de ese protocolo de coherencia. Entonces, el acceso a esa memoria (que es físicamente la misma pieza del material que sea) es directo y no llama implícitamente a la funcionalidad del protocolo de coherencia.

No sé qué tan rápido debería ser, no realicé ningún punto de referencia de este tipo, pero la idea es que, dado que ya no paga por este protocolo, ¡el acceso debería ser más rápido!

Por supuesto, la memoria compartida en las GPU NVIDIA se divide en bancos y si alguien quiere usarla para mejorar el rendimiento, debería echarle un vistazo antes. La razón es conflictos bancarios eso ocurre cuando dos hilos acceden al mismo banco y esto provoca la serialización del acceso..., pero eso es otra cosa aquí

Respondido el 25 de Septiembre de 20 a las 13:09

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.