INGENIERIA EN SISTEMAS COMPUTACIONALES INTEGRACION DE TECNOLOGIAS COMPUTACIONALES
CAPACIDAD COMPUTACIONAL -...
Transcript of CAPACIDAD COMPUTACIONAL -...
PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL
TECNOLOGÍA CUDA
45
IV. CAPACIDAD COMPUTACIONAL:
Debido al uso de la tecnología CUDA en computación numérica, se han
definido una serie de capacidades que ofrecen más información acerca
del rendimiento de los dispositivos CUDA en este campo.
NVIDIA define 5 clases de capacidad computacional
En la Tabla 1 se muestran algunas de las tarjetas comercializadas por
NVIDIA indicando la versión de Capacidad Computacional que le
corresponde y cuyo interés es relevante para el presente proyecto;
bien por su importancia en la literatura y las fuentes consultadas
como la 8800 y 5600, bien por su uso en alguna de las fases del
proyecto como 9500 y la 260 o bien para permitir una mejor
comparación con equipos más sofisticados especialmente diseñados para
las tareas computacionales de alto nivel como la familia Tesla.
Tarjeta Núcleos Multiprocesadores Capacidad
computacional
9500 GT 32 4 1.1
8800 ultra 128 16 1.0
260 GTX 192 24 1.3
FX5600 128 16 1.0
Tesla C870 128 16 1.0
Tesla C1060 240 30 1.3
Tesla C2050 448 14 2.0
Tabla 1: Resumen de algunas características de GPUs CUDA.
PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL
TECNOLOGÍA CUDA
46
En la Tabla 2 se indican las capacidades y especificaciones técnicas
del equipo disponibles según la versión soportada.
CAPACIDAD COMPUTACIONAL 1.0 1.1 1.2 1.3 2.0
Funciones atómicas de
enteros con operandos de
32 bits en memoria Global
NO Sí
Funciones atómicas de
enteros con operandos de
64 bits en memoria Global
NO Sí
Funciones atómicas de
enteros con operandos de
32 bits en memoria
Compartida
Funciones de voto Warp
Números con precisión
double.
NO Sí
Suma atómica de tipo float
con operandos de 32 bits
almacenados en memoria
compartida o en memoria
global. NO Sí
__ballot()
__threadfence_system()
__syncthreads_count(),
__syncthreads_and(),
__syncthreads_or() ;
Tamaño máximo de las
dimensiones “x” o “y” en
la definición de la Malla.
65535
Máximo número de hilos por
bloque.
512 1024
Tamaño máximo de las
dimensiones “x” o “y” en
la definición de un
Bloque.
512 1024
Tamaño máximo de la
dimensión “z” de un
bloque.
64
Tamaño del Warp.
32
Número máximo de bloques
residentes en un
multiprocesador.
8
Número máximo de Warps
residentes en un
multiprocesador.
24 32 48
Número máximo de hilos
residentes en un
multiprocesador.
768 1024 1536
Número de registros de 32
bits por Multiprocesador. 8K 16K 32K
Tamaño máximo de la
memoria compartida para
cada Multiprocesador.
16K 48K
Número de bancos de
memoria compartida. 16 32
Tamaño de memoria local
por hilo. 16KB 512KB
Tamaño de la memoria
constante. 64KB
Caché asignada a la
memoria constante por
multiprocesador.
8KB
PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL
TECNOLOGÍA CUDA
47
Caché asignada a la
memoria de Textura por
multiprocesador.
Entre 6KB y 8KB dependiendo del
dispositivo.
Anchura máxima para una
referencia 1D de textura
vinculada a un array CUDA.
8192 8192
Anchura máxima para una
referencia 1D de textura
vinculada a memoria
lineal.
2
Anchura y altura máximas
para una referencia 2D de
textura vinculada a
memoria lineal o a un
array CUDA.
65536 x 32768 65536 x
65536
Anchura altura y
profundidad máximas para
una referencia 3D de
textura vinculada a
memoria lineal o a un
array CUDA.
2048 x 2048 x 2048
4096 x
4096 x
4096
Número máximo de
instrucciones por kernel 2 millones
Tabla 2: Características del dispositivo según la Capacidad
Computacional.
1. ESTÁNDAR DE COMA FLOTANTE:
El estándar de la IEEE para aritmética en coma flotante (IEEE 754) es
el estándar más extendido y es usado por la mayoría de las CPUs y
GPUs. El estándar define formatos para la representación de números
en coma flotante (incluyendo el cero) y valores desnormalizados, así
como valores especiales como infinito y NaN, con un conjunto de
operaciones que usan este tipo de datos. También especifica cuatro
modos de redondeo y cinco excepciones (incluyendo cuándo ocurren
dichas excepciones y qué sucede en esos momentos).
IEEE 754 especifica cuatro formatos para la representación de valores
en coma flotante: precisión simple (32 bits), precisión doble (64
bits), precisión simple extendida (≥ 43 bits, no usada normalmente) y
precisión doble extendida (≥ 79 bits, usualmente implementada con 80
bits). Sólo los valores de 32 bits son requeridos por el estándar,
los otros son opcionales. Muchos lenguajes especifican qué formatos y
aritmética de la IEEE implementan, a pesar de que a veces son
opcionales. Por ejemplo, el lenguaje de programación C, ahora permite
pero no requiere la aritmética de la IEEE (el tipo de C float es
típicamente usado para la precisión simple de la IEEE y el tipo
double usa la precisión doble de la IEEE).
El título completo del estándar es “754-2008 IEEE Standard for
Floating-Point Arithmetic” que es la actualización del estándar “IEEE
Standard for Binary Floating-Point Arithmetic (ANSI/IEEE Std 754-
1985)”.
PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL
TECNOLOGÍA CUDA
48
Todos los dispositivos CUDA soportan el estándar IEEE 754-2008 con
las siguientes particularidades:
No existe un modo de redondeo configurable dinámicamente, la
mayoría de las operaciones soportan múltiples modos de
redondeo.
No hay un mecanismo para detectar si se ha producido una
excepción de tipo coma flotante. Todas las operaciones se
comportan como si las excepciones quedaran enmascaradas.
El resultado de una operación con precisión simple con
operandos tipo NaNs es el Nan discreto con el patrón de bits
0x7fffffff.
Las operaciones de valor absoluto y negativo en precisión doble
no cumplen el estándar. Los NaNs son pasados sin cambios.
2. PRECISIÓN SIMPLE EN DISPOSITIVOS DE CAPACIDAD COMPUTACIONAL 1.X:
La precisión simple o float se soporta según el estándar, pero con
las siguientes excepciones:
Los números desnormalizados no son soportados. La aritmético en
coma flotante y las instrucciones de comparación convierten
estos operandos en cero.
Subdesbordamiento: los valores son igualados a cero.
Algunas instrucciones no cumplen el estándar de la IEEE.
Las operaciones suma seguida de multiplicación son
generalmente combinadas en una operación única de
multiplicación-adición (FMAD), que trunca sin redondear la
mantisa del producto.
La división se implementa mediante la operación reciproca de
forma no estándar.
-La raíz cuadrada se implementa mediante la recíproca también
de forma no estándar.
Para las operaciones de producto y suma exclusivamente, el
redondeo al par más cercano y el redondeo a cero se implementan
mediante modos estáticos. El redondeo a + y – infinito no es
soportado.
PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL
TECNOLOGÍA CUDA
49
Para contrarrestar el efecto de estas restricciones, se proporcionan
implementaciones estándar aunque más lentas mediante las funciones
intrínsecas siguientes:
__fmaf_r{n,z,u,d}(float, float, float): Operación producto-
Adición fusionadas según el estándar de redondeo de IEEE.
__frcp_r[n,z,u,d](float): Operación de recíproco según el
estándar de redondeo de IEEE.
__fdiv_r[n,z,u,d](float, float): Operación de división según el
estándar de redondeo de IEEE.
__fsqrt_r[n,z,u,d](float): Operación de raíz cuadrada según el
estándar de redondeo de IEEE.
__fadd_r[u,d](float, float): Operación de Suma según el
estándar de redondeo de IEEE.
__fmul_r[u,d](float, float): Operación de Producto según el
estándar de redondeo de IEEE.
3. PRECISIÓN DOBLE EN DISPOSITIVOS DE CAPACIDAD COMPUTACIONAL 1.X:
En precisión doble o double sólo es soportado el modo de redondeo al
par más cercano para el reciproco, división, y raíz cuadrada según el
estándar IEEE.
Cuando se compila código para un dispositivo que no soporta double
(es decir, en dispositivos con capacidad computacional 1.2 o
anterior) cada variable double es convertida en una float(aunque el
dato sigue estando almacenado en 64 bits) y las operaciones
aritméticas se realizan en precisión simple. Para dispositivos tipo
2.0 y superior, el código debe ser compilado con las instrucciones -
ftz=false, -prec-div=true, y -prec-sqrt=true para garantizar que el
código es acorde al estándar IEEE. Para dispositivos tipo 1.x se debe
compilar con las ordenes: -ftz=true, -prec-div=false, y -prec-
sqrt=false.
La suma seguida de multiplicación son fusionadas habitualmente en una
única operación multiplicación-suma llamada FMAD con dos versiones
para ser compatible con cada uno de los dos tipos de capacidad
computacional.
FMAD para precisión simple en dispositivos de capacidad 1.x.
FFMA para precisión simple en dispositivos de capacidad 2.0.
La operación FMAD trunca la mantisa antes de usar el dato en la fase
de suma. En cambio la FFMA cumple con el estándar IEEE-754(2008) y se
usa la extensión complete del dato de la multiplicación en la
instrucción de suma y se produce un único redondeo al final de la
operación completa.
PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL
TECNOLOGÍA CUDA
50
4. DISPOSITIVOS DE CAPACIDAD COMPUTACIONAL TIPO 1.X:
a. ARQUITECTURA:
En dispositivos de capacidad computacional 1.x un multiprocesador
está formado por:
8 núcleos dedicados a operaciones aritméticas de enteros y coma
flotante de precisión sencilla.
1 unidad para aritmética de coma flotante con precisión doble.
2 unidades funcionales especiales para operaciones
trascendentales con operandos de precisión sencilla. (Estas
unidades pueden también realizar productos en coma flotante
sencilla).
-1 organizador de Warps: Para ejecutar una instrucción para un
Warp el organizador debe ejecutar las órdenes según el siguiente
esquema:
- 4 ciclos de reloj para operaciones aritméticas de enteros y
floats.
- 32 ciclos de reloj para operaciones con precisión doble.
- 16 ciclos de reloj para operaciones trascendentales con
precisión float.
Un Multiprocesador dispone de una Caché constante de lectura que es
compartida entre las unidades funcionales que acelera y lee de la
memoria constante del dispositivo.
Los Multiprocesadores son agrupados en Clusers de Procesado de
Textura (Texture Processor Clusters (TPCs)).
2 Multiprocesadores por TPC en dispositivos de tipo 1.0 y 1.1.
3 Multiprocesadores por TPC en dispositivos de tipo 1.2 y 1.3.
Cada TPC tiene una Caché de lectura en modo sólo lectura que es
compartida por todos los multiprocesadores y que acelera y lee del
espacio de memoria de Textura del dispositivo. Cada Multiprocesador
accede a la cache de textura mediante una unidad de textura que
implementa los distintos modos de llamada y de filtrado.
b. MEMORIA GLOBAL
El acceso a la memoria Global por parte de un Warp se divide en dos
solicitudes, una para cada medio-Warp, que son emitidas de manera
independiente. Los accesos de memoria de los hilos de cada medio-Warp
son amalgamados en una o más solicitudes de transacciones de memoria
dependiendo de la capacidad computacional del dispositivo.
PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL
TECNOLOGÍA CUDA
51
En un dispositivo tipo 1.0 o 1.1 las solicitudes amalgamadas de cada
medio Warp se ejecutan del siguiente modo:
El tamaño de las palabras accedidas por un hilo deben ser de 4,
8 o 16 bytes.
Si el tamaño es 4, las 16 palabras deben estar dispuestas en el
mismo segmento de 64 bytes.
Si el tamaño es 8, las 16 palabras deben estar dispuestas en el
mismo segmento de 128 bytes.
Si el tamaño es 16, las primeras 8 palabras deben estar
dispuestas en el mismo segmento de 128 bytes, y las 8 palabras
siguientes dispuestas en el bloque siguiente de 128 bytes.
Los accesos deben ser secuenciales, de tal modo que el enésimo-
hilo del medio-warp debe acceder a la enésima palabra.
Si el medio-warp cumple estos requisitos se emiten sendas órdenes de
transacción de memoria de 64 bytes, 128 bytes y de dos de 128 bytes.
La amalgama se realiza incluso en caso de divergencia en la
ejecución. En el caso de que no se cumplan estas condiciones se
emiten 16 órdenes de 32 bytes.
En un dispositivo tipo 1.2 o 1.3 los hilos pueden acceder a los datos
en cualquier orden, incluso acceder a los mismos datos. En esos
casos, una instrucción única de acceso de memoria es emitida por el
medio-Warp. A diferencia de lo que pasa en los dispositivos 1.0 y
1.1; donde el acceso debe ser secuencial y la amalgama sólo se
produce si el medio-Warp accede a un único segmento de memoria.
PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL
TECNOLOGÍA CUDA
52
El protocolo siguiente es el seguido para establecer las
transacciones necesarias para ejecutar las solicitudes emitidas por
los hilos de un Medio-Warp:
-Localizar el segmento de memoria que contiene las direcciones
solicitadas por el hilo activo de número más bajo.
El tamaño del segmento dependerá del tamaño de las palabras
solicitadas:
o -32 bytes para palabras de 1 byte.
o -64 bytes para palabras de 2 byte.
o -128 bytes para palabras de 4, 8 y 16 bytes.
-Localizar los demás hilos activos con solicitudes dirigidas al
mismo segmento.
-Reducir si es posible el tamaño de la transacción.
-Si el tamaño de la transacción es de 128 bytes y sólo se usa
una de las mitades se reduce el tamaño a 64 bytes.
-Si el tamaño de la transacción es de 64 bytes y sólo se usa
una de las mitades se reduce el tamaño a 32 bytes.
-Ejecutar las solicitudes y desactivar los hilos.
-Repetir hasta desactivar todos los hilos.
c. MEMORIA COMPARTIDA:
La memoria Compartida tiene 16 bancos organizados sucesivamente. Cada
banco tiene un ancho de banda de 32 bits por cada dos ciclos de
reloj.
La solicitud de memoria compartida realizada por un Warp se separa en
dos solicitudes de memoria. Una para cada medio-Warp que son
emitidas de manera independiente. Como consecuencia no debería haber
conflictos entre los hilos pertenecientes a cada medio-Warp.
Si una instrucción no-atómica, que es ejecutada por un Warp, pretende
que más de un hilo del warp escriban en la misma posición de la
memoria compartida, entonces sólo un hilo de cada medio-Warp
realizará la operación de escritura. De hecho queda indefinido cuál
de los hilos realiza la última escritura.
Accesos en zancadas de 32 bits (strided):
Un modo de acceso habitual para los hilos es a palabras de 32 bits
desde un array indexado por la identidad del hilo tid y con un tamaño
determinado de zancada “s”, por ejemplo mediante el código siguiente:
__shared__ float shared[32];
float data = shared[BaseIndex + s * tid];
PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL
TECNOLOGÍA CUDA
53
En este caso los hilos tid and tid+n acceden al mismo banco siempre y
cuando s*n sea un múltiplo del número de bancos (p.e. 16) o de manera
similar cuando n es un múltiplo de 16/d donde d el máximo común
divisor de 16 y “s”. En ese caso se evitará que haya conflicto de
acceso a bancos siempre que el tamaño del medio-warp (16) sea igual o
inferior a 16/d lo cual se cumple cuando d es 1 y por tanto s es
impar.
Accesos en modo Difusión (Broadcast):
Las memorias compartidas disponen de un mecanismo de difusión donde
las palabras de 32 bits pueden ser accedidas y difundidas a varios
hilos simultáneamente, ejecutando una sola instrucción de acceso de
memoria. Este método reduce enormemente los conflictos de banco
cuando varios hilos leen direcciones del mismo dato de 32 bits. De
hecho para el caso de accesos de memoria realizados a varias
posiciones, lo que se hace es dividir el proceso en varios pasos;
ejecutando tantos accesos como sean necesarios para evitar conflictos
hasta completar todas las solicitudes. En cada paso se maneja un
grupo de direcciones según el protocolo siguiente:
Asignar una de las palabras apuntadas por las demás direcciones
como dirección de difusión.
Incluir en el grupo de direcciones:
- Todas las direcciones que están dentro de la palabra de
difusión.
- Una dirección para cada banco (excepto la del banco de
difusión) apuntadas por las demás direcciones.
Un ejemplo donde se evitan conflictos es cuando todos los hilos de un
medio-warp leen posiciones de memoria contenidas en la misma palabra
de 32 bits.
Accesos de 8 y 16 bits:
Los accesos de tipo 8-bit y 16-bit producen generalmente conflictos
de banco, por ejemplo cuando un array de tipo char es accedido
mediante:
__shared__ char shared[32];
char data = shared[BaseIndex + tid];
Esto es debido a que shared[0], shared[1], shared[2], y shared[3],
por ejemplo, pertenecen al mismo banco. Sin embargo se evitarían
conflictos si se accediesen mediante:
char data = shared[BaseIndex + 4 * tid];
Accesos de más de 32 bits:
Los accesos mayores de 32 bits por hilo son divididos en accesos de
32. Usualmente provocan conflictos de banco.
PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL
TECNOLOGÍA CUDA
54
En el ejemplo siguiente aparecen conflictos bi-direccionales para
arrays de tipo double cuando se acceden mediante:
__shared__ double shared[32];
double data = shared[BaseIndex + tid];
Ya que la solicitud de memoria se compila en dos solicitudes de 32
bits cada una con una zancada de 2. Para evitar estos conflictos se
divide los operandos double como se muestra en el siguiente ejemplo:
__shared__ int shared_lo[32];
__shared__ int shared_hi[32];
double dataIn;
shared_lo[BaseIndex + tid] = __double2loint(dataIn);
shared_hi[BaseIndex + tid] = __double2hiint(dataIn);
double dataOut =
__hiloint2double(shared_hi[BaseIndex + tid],
shared_lo[BaseIndex + tid]);
Este método puede no mejorar las prestaciones e incluso empeorarlas
en dispositivos tipo 2.0.
Lo mismo es aplicable a asignación de estructuras:
__shared__ struct type shared[32];
struct type data = shared[BaseIndex + tid];
Que se traduce en:
3 lecturas independientes sin conflicto si type se
define como: struct type {float x, y, z;}; ya que cada elemento
es accedido con una zancada impar de tres palabras de 32 bits.
-2 lecturas independientes con conflicto si type
se define como: struct type {float x, y;}; ya que cada elemento
es accedido mediante una zancada par de dos palabras de 32bits.
PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL
TECNOLOGÍA CUDA
55
5. DISPOSITVOS DE CAPACIDAD COMPUTACIONAL 2.0:
a. ARQUITECTURA:
Para dispositivos de tipo 2.0 un Multiprocesador está compuesto por:
-32 núcleos CUDA para operaciones aritméticos con enteros y con
operandos en coma flotante.
-4 unidades funcionales especiales para funciones
trascendentales con operandos de coma flotante.
2 organizadores de Warps.: Cada organizador genera una
instrucción para que sea ejecutada por un Warp active. El
primer organizador gestiona los Warps con índice impar y el
segundo se encarga de los de índice par. Sin embargo cuando un
organizador manda una orden en precisión double el otro
organizador permanece inactivo.
Un organizador manda instrucciones a la mitad de los núcleos.
Por tanto para ejecutar una orden para todos los hilos de un
warp, el organizador debe emplear:
- 2 ciclos de reloj para una instrucción aritmética de
tipo entero o float.
- 2 ciclos de reloj para una instrucción aritmética de
tipo double.
- 8 ciclos de reloj para una instrucción trascendental de
tipo float.
Un Multiprocesador posee una única Caché en modo solo lectura que se
comparte por todas las unidades funcionales y acelera los accesos de
la memoria constante del dispositivo.
Existe una memoria caché L1 para cada Multiprocesador y una Caché L2
compartida por todos los Multiprocesadores, ambas son usadas para
almacenar los accesos a las memorias local y global del dispositivo.
El comportamiento de la Caché y su gestión pueden ser parcialmente
configuradas usando modificadores de las instrucciones de carga y
almacenamiento.
La misma memoria de chip se usa tanto para la memoria compartida como
para L1: Se puede configurar como 48KB de memoria compartida con 16
KB de Caché L1 o como 16KB de memoria compartida y 48 KB de L1 usando
la instrucción cudaFuncSetCacheConfig() o cuFuncSetCacheConfig(),
como ilustra el siguiente ejemplo de código:
// Device code
__global__ void MyKernel()
{
...
}
PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL
TECNOLOGÍA CUDA
56
// Host code
// Runtime API
// cudaFuncCachePreferShared: shared memory is 48 KB
// cudaFuncCachePreferL1: shared memory is 16 KB
// cudaFuncCachePreferNone: no preference
cudaFuncSetCacheConfig(MyKernel, cudaFuncCachePreferShared)
// Driver API Appendix G. Compute Capabilities 156 CUDA C
Programming Guide Version 3.1
// CU_FUNC_CACHE_PREFER_SHARED: shared memory is 48 KB
// CU_FUNC_CACHE_PREFER_L1: shared memory is 16 KB
// CU_FUNC_CACHE_PREFER_NONE: no preference
CUfunction myKernel;
cuFuncSetCacheConfig(myKernel, CU_FUNC_CACHE_PREFER_SHARED)
Los Multiprocesadores se agrupan en Razimos de Procesamiento Gráfico
(Graphics Processor Clusters (GPCs)). Cada GPC está compuesto por 4
Multiprocesadores.
Cada Multiprocesador posee una Caché Textura en modo sólo lectura de
la memoria de Textura del dispositivo. Se accede a dicha Caché
mediante una unidad particular que implementa los modos de
direccionamiento y filtros de datos.
b. MEMORIA GLOBAL:
Los accesos a la memoria Global son almacenados en la Caché usando la
bandera de compilación–dlcm que permite configurar el destino de
almacenamiento L1 o L2.
Para almacenar en L1 y L2 se usa la instrucción por defecto:(-Xptxas
-dlcm=ca) y para almacenar en L2 se usa la instrucción (-Xptxas -
dlcm=cg).
Una línea de memoria tanto en L1 como en L2 es de 128 bytes y es
mapeada a un segmento del mismo tamaño en la memoria del dispositivo.
Si el tamaño de los datos accedidos por cada hilo es superior a 2
bytes, entonces los accesos del warp se dividen en solicitudes de 128
bytes independientes entre sí de tal modo que:
2 solicitudes de memoria; una para cada medio-warp si el tamaño
es de 8 bytes.
4 solicitudes de memoria; una para cada medio-warp si el tamaño
es de 16 bytes.
PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL
TECNOLOGÍA CUDA
57
Cada solicitud de memoria se divide en solicitudes de Caché que se
ejecutan independientemente.
Los hilos pueden acceder a cualquier dato en cualquier orden, incluso
acceder a los mismos datos.
Si una instrucción no-atómica ejecutada por un warp escribe en la
misma posición de la memoria global, sólo un hilo realizará dicha
escritura.
c. MEMORIA COMPARTIDA:
La memoria compartida se organiza en 32 bancos de tal modo que
palabras consecutivas de 32 bits son asignadas a bancos consecutivos.
Cada banco posee un ancho de banda de 32 bits por cada dos ciclos de
reloj. Por tanto a diferencia de los dispositivos tipo 1.x, puede
haber conflicto entre hilos perteneciente a distintas mitades del
mismo warp.
Un conflicto se produce sólo si 2 o más hilos acceden a un byte
cualquiera perteneciente a palabras distintas de 32 bytes
pertenecientes al mismo banco. Si dos o más hilos acceden a cualquier
byte contenido en la misma palabra de 32 bits, no habrá conflicto. Ya
que para accesos de lectura, el dato es difundido a los hilos que lo
solicitan(A diferencia de lo que sucede en dispositivos tipo 1.x ;
varios datos pueden se difundidos mediante una única transacción).
Para procesos de escritura, cada byte es escrito por un único hilo.
Esto significa en concreto que a diferencia de los dispositivos tipo
1.x no hay conflicto de banco si un array de tipo char se accede de
la manera siguiente:
__shared__ char shared[32];
char data = shared[BaseIndex + tid];
Acceso de 32 bits en modo zancada (strided):
Es habitual que un hilo acceda a datos de 32 bits a partir de un
array indexado por índice del hilo tid y con una zancada “s”:
__shared__ float shared[32];
float data = shared[BaseIndex + s * tid];
En este caso los hilos tid y tid+n acceden al mismo banco siempre y
cuando s*n sea un múltiplo del número de bancos (es decir de 32), o
lo que es lo mismo, siempre que n sea múltiplo de 32/d donde “d” es
el mayor divisor común de 32 y “s”. Por tanto no habrá conflicto
siempre y cuando el tamaño del warp (32) sea igual o inferior a 32/d,
es decir cuando “d” sea igual a uno y, por tanto, cuando “s” sea
impar.
Accesos superiores a 32 bits:
Los accesos de tipo 64-bit y 128-bit son gestionados de tal modo que
se minimizan los conflictos de banco como se describe más adelante.
PROGRAMACIÓN EN EL ENTORNO CUDA EN APLICACIONES DE MECÁNICA COMPUTACIONAL
TECNOLOGÍA CUDA
58
Los demás accesos superiores a 32 bits son divididos en accesos de
32, 64 o 128 bits.
El siguiente código produce 3 lecturas independientes de 32 bits sin
conflictos de banco ya que cada elemento es accedido con una zancada
de 3 palabras de 32 bits:
struct type {
float x, y, z;
};
__shared__ struct type shared[32];
struct type data = shared[BaseIndex + tid];
Accesos de 64-bits.
Para los accesos de 64 bits se puede producir un conflicto de banco
únicamente si 2 o más hilos de cada medio-warp acceden a direcciones
diferentes pertenecientes al mismo banco.
A diferencia de los dispositivos tipo 1.x, no se producen conflictos
para arrays de tipo double en el ejemplo de código siguiente:
__shared__ double shared[32];
double data = shared[BaseIndex + tid];
Accesos de 128-bits.
La mayoría de los accesos de 128 bits producirán conflictos
bidireccionales, incluso si no hay hilos pertenecientes a un cuarto-
de-warp que accedan a diferentes direcciones pertenecientes al mismo
banco. Por tanto para determinar las rutas de los conflictos de
banco, se debe incrementar en uno el máximo número de hilos de un
cuarto-de-warp que acceden a distintas direcciones del mismo banco.
d. MEMORIA CONSTANTE:
Además de la memoria constante del dispositivo, los dispositivos de
capacidad 2.0 soportan La Instrucción de Carga Uniforme (LDU (LoaD
Uniform)), que permite al compilador almacenar cualquier variable
que:
Apunte a la memoria global.
Sea de sólo lectura en el Kernel.
Sea independiente de la identidad del hilo.