(4) Explicación detallada de la interfaz de programación de aplicaciones CUDA

extensión del lenguaje C

La interfaz de programación de CUDA es un conjunto de extensiones del lenguaje C, la principal es la biblioteca Runtime, que se divide en tres componentes: componente host, componente de dispositivo y componente público.

Componente de host: se ejecuta en el host y proporciona funciones para controlar y acceder a uno o más dispositivos informáticos.

Componentes del dispositivo: los dispositivos ejecutan y proporcionan funciones específicas del dispositivo.

Componentes comunes: un subconjunto de la biblioteca estándar de C que proporciona tipos de vectores integrados y compatibilidad con codificaciones tanto de host como de dispositivo.


hacer una pregunta

  1. Definición de función y declaración de calificación del tipo de declaración, ¿cómo se llaman las funciones del host y el dispositivo (GPU)?
  2. ¿Cómo definir la ubicación de la memoria y el tamaño de la GPU?
  3. ¿Cómo se ejecuta una función del kernel en el dispositivo?
  4. ¿Cómo llamar a los indicadores de bloques e hilos de GPU?
  5. ¿Cómo compilar código CUDA?

declaración de calificación del tipo de función

__device__ // 在设备上执行,仅可从设备上调用

__global__ // 限定一个函数作为kernel的存在,在设备上执行,仅可从主机上调用

__host__ // 在主机上执行,仅可从主机上调用

/*
__host__ 可以与 __device__ 限定词的组合,支持主机和设备双向编译
*/

/*
限定:
__device__ 和 __global__ 不支持递归、内部不能声明静态变量、不能有自变量的一个变量数字
__device__ 不能拿到函数地址,另外,函数指向 __global__ 是支持的
__global__ 和 __host__ 不能一起使用
__global__ 函数必须要void的返回类型,调用到__global__函数必须指定它的执行配置
__global__ 函数的调用是同步的,意味着设备执行完成前返回
__global__函数参数目前是通过共享内存到设备的,并且被限制在256 个字节
*/

declaración de calificación de tipo variable

__device__
__device__Un calificador declara una variable que reside en el dispositivo.
Como máximo, se define otro calificador de tipo en los siguientes tres elementos, que se
pueden __device__usar juntos para especificar con más detalle a qué espacio de memoria pertenece la variable.

  1. residen en el espacio de memoria global
  2. tiene una vida útil de la aplicación
  3. Accesible desde todos los subprocesos dentro del grid y desde el host a través de la biblioteca en tiempo de ejecución

__constant__
__constant__calificador, __device__usado aleatoriamente, declara una variable

  1. residen en un espacio de memoria constante
  2. tiene una vida útil de la aplicación
  3. son accesibles desde todos los subprocesos dentro del grid y desde el host a través de la biblioteca en tiempo de ejecución

__shared__
__shared__El calificador, __device__usado con select, declara una variable:

  1. reside en el espacio de memoria compartida del bloque de subprocesos
  2. tiene una vida útil de un bloque
  3. Solo se puede acceder a todos los hilos dentro del bloque.

Las variables compartidas entre subprocesos tienen coherencia secuencial completa.
Solo __syncthreads()se garantiza que la función en ejecución será visible en las escrituras de otros subprocesos
. A menos que la variable se defina como volátil, el compilador es libre de optimizar la memoria compartida tan pronto como se alcance el estado anterior de lectura y escritura. en

Al declarar una variable en la memoria compartida como una matriz externa, por ejemplo, extern_shared_float shared[];
el tamaño de la matriz se determina en el momento del envío. Todas las variables declaradas de esta manera comienzan en la misma dirección en la memoria,
por lo que la ubicación de las variables en la matriz debe offsetcontrolarse explícitamente mediante (desplazamiento).
Por ejemplo, si desea definir

short array0[128];
float array1[64];
int array2[256];

En la memoria compartida asignada dinámicamente, puede definir una matriz de la siguiente manera

extern __shared__ short array[];
__device__ void func() //__device__or__global__function
{
    
    
 Short* array0 = (short*)array;
 float* array1 = (float*)&array0[128];
 int* array2 = (int*)&array1[64];
} 

limitado

Estos calificadores no permiten que los miembros structy unionlos parámetros formales y las variables locales de la función se ejecuten en el host.

__shared__y __constant__las variables implican almacenamiento estático

__device__,__shared__y __constant__las variables no se pueden externdefinir con la palabra clave para uso externo

__device__y __constant__las variables solo están permitidas en el alcance del archivo

__constant__Las variables no se pueden asignar desde el dispositivo, solo se pueden asignar desde el host a través de la función de tiempo de ejecución del host

__shared__Las variables no se pueden inicializar como parte de su declaración.

Un argumento declarado en el código del dispositivo sin ningún calificador generalmente reside en un registro.

Aunque en algunos casos el compilador puede optar por colocarlo en la memoria local.

Por lo general, en este caso, una estructura o matriz grande consumirá mucho espacio de registro y el compilador no puede estar seguro de que la matriz esté indexada con un número constante.

ptxLa inspección del código ensamblador (obtenido al compilar con - ptxla -keepopción o) indicará si la variable se colocó en la memoria local durante la primera fase de compilación, por lo que se declarará usando el .localmnemónico o se accederá usando ld.localel mnemónico yst.local

Si no es así, incluso si descubren que consume demasiado espacio de registro para la arquitectura de destino, las fases de compilación posteriores aún pueden tomar otras decisiones.

Se puede generar --ptxas-option=-vun informe de uso de memoria local (lmem) usando .

Soporte para códigos de puntero ejecutados en el dispositivo, cuando el compilador puede resolver si apuntan al espacio de memoria global o al espacio de memoria local.

De lo contrario, estarán restringidos para que apunten únicamente a la memoria direccionada o al espacio declarado en la memoria global.

Libere un puntero al código en la memoria global o compartida que se ejecuta en el host, o al código de la memoria del host que se ejecuta en el dispositivo

El resultado es un comportamiento indefinido, que provoca un error de fragmento o la finalización de la aplicación.

La dirección solo se puede obtener a través de la variable __device__,__shared__o en el código del dispositivo__constant__

donde __device__o __constant__la dirección de una variable sólo se puede cudaGetSymbolAddress()obtener mediante


ejecutar configuración

Todas __global__las llamadas a funciones deben especificar una configuración de ejecución.

Las configuraciones de ejecución definen las dimensiones de cuadrícula y bloque de las funciones que normalmente se ejecutan en el dispositivo, y de manera similar stream.

<<< Dg, Db, Ns,S>>>Se especifica insertando entre el nombre de la función y los argumentos encerrados entre paréntesis.

Dges el tipo dim3y especifica la dimensión y el tamaño del ráster, de modo que Dg.x * Dg.ysea igual al número de bloques que se envían;

Dbes tipo dim3y especifica las dimensiones y tamaño de cada bloque, de manera que Db.x * Db.y * Db.zsea igual al número de subprocesos por bloque;

Nses de tipo size_t y especifica el número de bytes en la memoria compartida que se asigna dinámicamente para cada bloque además de la memoria asignada estáticamente; esta memoria asignada dinámicamente es utilizada por cualquier variable declarada como una matriz externa, Ns es un parámetro opcional que se establece de forma predeterminada a 0.

Ses de tipo cudaStream_t y especifica la secuencia asociada; Ses un número opcional cuyo valor predeterminado es 0

Como ejemplo, la función se declara como

__global__ void Func(float* parameter);

debe llamarse así:

Func<<< Dg, Db, Ns >>>(parameter);

Los parámetros de función que realizan la configuración se evalúan antes de ser llamados y pasados ​​al dispositivo a través de la memoria compartida.

Si Dg o Db es mayor que el máximo permitido por el dispositivo, o el valor de Ns es mayor que menos (memoria asignada estáticamente en la memoria compartida, parámetros de función y configuración de ejecución)), la función no se llamará


variables incorporadas

/*
gridDim
这个变量是类型dim3 (参见4.3.1.2 部分)并且包含栅格的维数。

blockIdx
这变量是类型uint3 (参见4.3.1.1 部分)并且包含栅格之内的块索引。

blockDim
这变量是类型dim3 (参见4.3.1.2 部分)并且包含在块的维数。

threadIdx
这变量是类型uint3 (参见4.3.1.1 部分)并且包含块之内的线程索引。

限定
 内置变量不允许取得任何地址。
 不允许赋值到任何内置变量。 

*/


Compilación NVCC

nvcces la abreviatura de controlador de compilación para el proceso de compilación de código CUDA: proporciona opciones de línea de comandos simples y familiares y las ejecuta invocando herramientas que implementan una colección de diferentes fases de compilación.

nvccEl flujo de trabajo básico consiste en separar el código del dispositivo del código del host y compilar el código del dispositivo en un formato binario u cubinobjeto.

Salida de código host generado, ya sea como código C enviado para compilación usando otras herramientas o como código objeto que invoca directamente al compilador host durante la etapa de compilación final.

Las aplicaciones pueden simplemente ignorar el código de host generado y usar CUDAel objeto controlador APIcargado en el dispositivo cubino vincular el código de host generado.

El código incluye objetos como una matriz de datos inicializada globalmente cubiny contiene un modificador que implementa la sintaxis de configuración e ingresa el CUDA Runtimecódigo de inicio necesario para cargar y enviar cada uno de los compilados Kernel.

La sintaxis que el compilador procesa CUDAen la parte frontal del archivo fuente cumple totalmente conC++

El código de host es totalmente compatible C++. Pero los códigos de dispositivo sólo admiten un subconjunto C++de C;

No se admiten C++funciones en bloques básicos , como: o declaraciones de variablesclasses, inheritance

Como resultado del uso C++de la sintaxis, voidun puntero (por ejemplo, malloc()devolviendo) typecastno se puede asignar a non-voidun puntero sin usarlo.

A continuación se presentan las dos detecciones del compilador de NVCC.

__noinline__

De forma predeterminada, __device__las funciones siempre están en línea.

__noinline__La función se puede utilizar como sugerencia para una función no en línea.

La función en sí debe colocarse en el archivo de llamada y el compilador no puede garantizar que __noinline__los calificadores de funciones con parámetros de puntero y funciones con listas de parámetros grandes funcionen correctamente.

#pragma unroll

De forma predeterminada, el compilador desenrolla pequeños bucles para recuentos de viajes conocidos.

#pragma unrollPuede detectar y controlar cualquier bucle desenrollado.

Debe colocarse antes de este bucle y solo afecta a este bucle. Al mismo tiempo, puede especificar cuántas veces se puede expandir el bucle mediante un parámetro.

Por ejemplo:

#pragma unroll 5
For (int i = 0; i < n; ++i)

El bucle se desenrollará 5 veces. Asegúrese de que la acción de expansión no afecte la corrección del programa.
Si #pragma unrollno hay ningún valor adjunto, el bucle se desenrolla completamente cuando el recuento de viajes es constante; de ​​lo contrario, no se desenrollará


Componentes de tiempo de ejecución públicos

Los componentes comunes de Runtime pueden ser utilizados tanto por funciones del host como del dispositivo

内置矢量类型
char1, uchar1, char2, uchar2, char3, uchar3, char4, uchar4, short1, ushort1,
short2, ushort2, short3, ushort3, short4, ushort4, int1, uint1, int2, uint2, int3,
uint3, int4, uint4, long1, ulong1, long2, ulong2, long3, ulong3, long4, ulong4,
float1, float2, float3, float4

Estos tipos de vectores se derivan de los tipos básicos de punto flotante y entero.

Son estructuras y se puede acceder a los componentes 1.º, 2.º, 3.º y 4.º a través de los campos x, y, z,y wrespectivamente

Todos toman una make_<type name>función constructora del formato.

Ejemplo: crear un vector de tipo int2 make_int2(int x, int y);por asignación(x, y)int2

dim3 类型 

Este tipo es uint3un tipo de vector entero para especificar dimensiones basadas en

Al definir una dim3variable de tipo, todos los componentes restantes no especificados se inicializan para1

数学函数

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí
Table B-1Las funciones matemáticas se pueden utilizar como funciones de host y dispositivo, y los límites de error de cada función se han probado intensamente, pero no se garantiza que sean absolutamente correctos.

La suma y la multiplicación cumplen con IEEE, por lo que tienen un error máximo de 0,5 ulp. Por lo general, se combinan en una instrucción de suma múltiple (FMAD)

Recomendamos usar rintf() en lugar de roundf() al calcular números de punto flotante a enteros.

Porque roundf() asigna 8 secuencias de instrucciones mientras que rintf() asigna solo una instrucción

truncf(), ceilf() y Floorf() también asignan solo una instrucción

La biblioteca de tiempo de ejecución CUDA también admite números enteros min() y max(), que también asignan una instrucción.

inserte la descripción de la imagen aquí

Table B-2La función solo se puede utilizar como función del dispositivo.

Sus límites de error son específicos de la GPU.

Aunque estas funciones tienen menor precisión, son mucho más rápidas que algunas de las funciones de la Tabla B-1; tienen el mismo prefijo

__fadd_rz(x,y)Calcule los parámetros de punto flotante xey con redondeo a cero y el parámetro de punto flotante xx__fmul_rz(x,y) con redondeo a cerox y y y producto

La división regular de punto flotante __fdividef(x,y)tiene la misma precisión, pero para 2 126 < y < 2 128 2^{126} < y < 2^{128}2126<y<2128

__fdividef(x,y)El resultado es cero y la división normal puede obtener el resultado correcto. De manera similar, para 2 126 < y < 2 128 2^{126}< y <2^{128}2126<y<2128 sixxx es infinito

__fdividef(x,y)El resultado es NaN(el resultado es infinito multiplicado por cero), y la división regular devuelve infinito

__[u]mul24(x,y)Calcula el producto de los 24 bits menos significativos de los argumentos enteros xey y da los 32 bits menos significativos del resultado. xxx y y Se ignoran los 8 bits más significativos de y .

__[u]mulhi(x,y)Calcular el parámetro entero xxx y y y y da los 32 bits más significativos del resultado de 64 bits

__[u]mul64hi(x,y)Calcular el parámetro entero de 64 bits xxx y y y y da los 64 bits más significativos del resultado de 128 bits

__saturate(x)si xxx es menor que 0, devuelve 0; sixxDevuelve 1 si x es mayor que 1; si x está entre [0, 1], devuelvexxX

__[u]sad(x,y,z)(Sum of Absolute Difference )Encuentra el parámetro entero zzz y parámetro enteroxxx y y la suma de los valores absolutos de las diferencias y

__clz(x)Calcula el parámetro entero de 32 bits xxceros a la izquierda de x

__clzll(x)Calcular el parámetro entero de 64 bits xxceros a la izquierda de x

__ffs(x)Devuelve la posición del primer bit del parámetro entero x. La posición de bit menos significativa es 1 si x es 0

__ffs()devuelve 0. Esto es lo mismo que la función ffs de Linux.

__ffsll(x)Devuelve el parámetro entero de 64 bits xxLa posición del primer bit de x es 1. La posición de bit menos significativa es 1 si x es 0

__ffsll()devuelve 0. Esto es lo mismo que la función ffsll de Linux.

时间函数 

clock_t clock();El valor de retorno del contador incrementa cada ciclo de reloj.

Muestre este contador al kernelprincipio y al final y tome la diferencia de las dos muestras.

Y registra los resultados obtenidos al ejecutar el hilo completamente a través del dispositivo por ciclo de reloj por hilo.

en lugar del número de ciclos de reloj realmente gastados por el dispositivo que ejecuta las instrucciones del hilo

El primer número es mayor que el segundo porque los hilos se cortan en segmentos de tiempo.

纹理类型

CUDAAdmite un subconjunto de renderizado de texturas por hardware mediante GPUel uso de memoria de texturas para gráficos

La lectura de datos a través de la memoria de textura tiene muchas ventajas de rendimiento sobre la memoria global

La memoria de textura se lee texture fetchesa través de una función del dispositivo llamadakernel

Texture fetchEl primer parámetro de especifica un texture refereceobjeto llamado

Texture referenceDefine qué parte de la memoria de textura esfetch

Antes de que pueda kernelusarse, runtimelas funciones del host deben vincularlo a alguna región de memoria.

Algunos texture referencepueden estar vinculados bajo la misma textura o en la memoria de un mapa de texturas.

Texture referencetiene algunas propiedades

Uno de ellos es que puede especificar si la textura utiliza una matriz unidimensional direccionada a través de una coordenada de textura.

O especifique si la textura utiliza una matriz bidimensional direccionada por dos coordenadas de textura.

Los elementos de la matriz se denominan simplemente texels(elementos de textura)

Otro atributo es fetchdefinir los tipos de datos de entrada y salida para la textura.

Texture Reference 声明

Algunos texture referencede los atributos son fijos, texture referencese especifican al declarar el archivo . Una variable se declara texture referenceen el ámbito del archivo como un tipo.texture

Texrure<Type, Dim, ReadMode> texRef;

en

  • TypeEl tipo de datos especificado se devuelve cuando se selecciona la textura.
  • DimEspecifica texture referencela dimensión de , que es igual a 1 o 2; Dimes un argumento opcional cuyo valor predeterminado es 1;
  • ReadModeigual a cudaReadModeNormalizedFloato cudaReadModeElementType; si es cudaReadModeNormalizedFloaty Typees un tipo entero 16-bito 8-bit, el valor devuelto en realidad se tratará como un tipo de punto flotante, al unsignedcual se asigna [0.0,1.0]el tipo entero ; por ejemplo, un valor con un texto de 8 bits sin signo leído como 0xff ; si es así , no se realizará ninguna conversión; es un argumento opcional cuyo valor predeterminado essigned[-1.0,1.0]1cudaReadModeElementTypeReadModecudaReadModeElementType
Runtime Texture Reference 属性 

Algunas texture referencede las propiedades no son fijas, pueden cambiarse mediante el tiempo de ejecución del host.

Pueden especificar si las coordenadas de textura son normalized, el modo de direccionamiento y el filtrado de textura.

De forma predeterminada, las texturas están [0,N)referenciadas mediante coordenadas de punto flotante, Nque son el tamaño de la textura en el espacio con respecto a las coordenadas.

Por ejemplo: una 64x32textura de tamaño tiene un rango de coordenadas en el eje x [0,63]y el eje y.[0,31]

NormalizedLa textura [0.0,1.0)está referenciada por coordenadas en lugar de[0,N)

Por lo tanto, la misma 64x32textura apuntará al normalizedeje x [0.0,1.0)y al eje y del[0.0,1.0)

NormalizedLas coordenadas de textura son inherentemente adecuadas para algunas aplicaciones, por ejemplo, las coordenadas de textura son independientes del tamaño de la textura.

El modo de direccionamiento define qué sucede cuando las coordenadas de textura se salen de los límites.

Cuando se utilizan unnormalizedcoordenadas de textura, cuando las coordenadas de textura están fuera de rango [0, N), el valor menor que 0 se establece en 0 y el valor mayor que N se establece en N-1

Cuando se utilizan normalizedcoordenadas de textura de , el rango de coordenadas de textura se limita a [0.0,1.0). Para las coordenadas de textura de , normalizedtambién se especifica warpel direccionamiento

warpEl direccionamiento se usa generalmente cuando la textura contiene una señal periódica que solo actúa en partes fraccionarias de las coordenadas de la textura.

Por ejemplo, 1,25 se tratará como 0,25 y -1,25 se tratará como 0,75.

El filtrado de textura lineal solo se puede utilizar si la textura está configurada para devolver datos de punto flotante

texelRealiza una interpolación de baja precisión en los texels adyacentes alrededor de los cuales se leerán las direcciones de recuperación de texturas.

y texeldevuelve el valor de recuperación de textura interpolado en función de las coordenadas de textura en las que

La interpolación simple se realiza en texturas 1D, bilinearla interpolación se realiza en texturas 2D

线性内存纹理操作对比CUDA 数组

Una textura se puede asignar en memoria lineal o en una matriz CUDA, las texturas se asignan en memoria lineal:

  • Sólo cuando la dimensión es 1;
  • No se admite el filtrado de texturas;
  • Sólo se puede utilizar direccionamiento de coordenadas de textura no normalizadas;
  • No se pueden admitir diferentes modos de direccionamiento: el acceso a texturas fuera de rango devuelve 0;

Componente de tiempo de ejecución del dispositivo

Los componentes del dispositivo runtimesolo se pueden utilizar en funciones del dispositivo.

同步函数:void __syncthreads();

Sincroniza todos los hilos dentro de un bloque. Una vez que todos los subprocesos hayan llegado a este punto, reanude la ejecución normal.

__syncthreads()Generalmente se usa para coordinar la comunicación de subprocesos entre el mismo bloque.

Cuando algunos subprocesos dentro de un bloque acceden a la misma memoria compartida o global

potencialmente peligroso para algunos accesos a la memoria read-after-write, write-after-read, owrite-after-write

Estos peligros de datos se pueden evitar sincronizando el acceso entre subprocesos.

__syncthreads()Se permite la colocación en código condicional, pero solo si todo el bloque de subprocesos tiene la misma condición en todo momento; de lo contrario, la ejecución del código puede bloquearse o causar efectos secundarios no deseados.

类型转换函数

El sufijo de las siguientes funciones especifica el modo de redondeo IEEE-754

rnes encontrar el número par más cercano

rzesta cerca de cero

ruse redondea hacia arriba (al infinito positivo)

rdse redondea hacia abajo (al infinito negativo)

int __float2int_[rn,rz,ru,rd](float);Convierte un argumento de punto flotante en un número entero usando el modo de redondeo especificado

Unsignde int __float2unit_[rn,rz,ru,zd](float);Convierte un argumento de punto flotante en un entero sin signo usando el modo de redondeo especificado

float __int2float_[rn,rz,ru,rd](int);Convierte un argumento entero en un número de coma flotante usando el modo de redondeo especificado

float __int2float_[rn,rz,ru,rd](unsigned int);Convierte un argumento entero sin signo en un número de coma flotante usando el modo de redondeo especificado

Type Casting 函数

float __int_as_float(int);Realiza una conversión de tipo de un flotante en un argumento entero, dejando el valor sin cambios

Por ejemplo, __int_as_float(0xC0000000)igual a-2

int __float_as_int(float);Una conversión de tipo entero realizada en un argumento de punto flotante, dejando el valor sin cambios

Por ejemplo, __float_as_int (1.0f)igual a0x3f800000

纹理函数
  1. tex1Dfetch()Operación de textura de la memoria del dispositivo: se accede a las texturas en la memoria del dispositivo a través de funciones
template<class Type>
Type tex1Dfetch(
texture<Type, 1, cudaReadModeElementType> texRef,
int x);
float tex1Dfetch(
texture<unsigned char, 1, cudaReadModeNormalizedFloat> texRef,
int x);
float tex1Dfetch(
texture<signed char, 1, cudaReadModeNormalizedFloat> texRef,
int x);
float tex1Dfetch(
texture<unsigned short, 1, cudaReadModeNormalizedFloat> texRef,
int x);
float tex1Dfetch(
texture<signed short, 1, cudaReadModeNormalizedFloat> texRef,
int x);

Estas funciones recuperan una región xen la memoria lineal vinculada mediante coordenadas de texturatexture reference texRef

Para tipos de números enteros, no se permiten el filtrado de texturas ni los modos de direccionamiento seleccionados. Para estas funciones, puede ser necesario actualizar los números enteros a 32-bitflotantes.

La siguiente función demuestra 2-el 4-soporte para tuplas.

float4 tex1Dfetch(
texture<uchar4, 1, cudaReadModeNormalizedFloat> texRef,
int x); 

xSelecciona texture reference texRefla región en la memoria lineal vinculada por coordenadas de textura

  1. Operación de textura de matriz CUDA: acceso desde la textura en la matriz CUDA a través de la función tex1D() o tex2D()
template<class Type, enum cudaTextureReadMode readMode>
Type tex1D(texture<Type, 1, readMode> texRef, float x);
template<class Type, enum cudaTextureReadMode readMode>
Type tex2D(texture<Type, 2, readMode> texRef, float x, float y); 

Estas funciones seleccionan la región CUDAen la matriz unida por las coordenadas de textura xeytexture reference texRef

Texture referenceLos atributos de tiempo de compilación (fijo) y tiempo de ejecución (variable) determinan cómo se interpretan las coordenadas, qué procesamiento se producirá cuando se recupera la textura y el valor devuelto por la recuperación de la textura.


función atómica

Las funciones atómicas solo están disponibles en dispositivos con Compute Compatibility 1.1

/*
1. atomicAdd()
从全局内存中读取地址为address 的32-bit 字old,计算(old + val),将结果返回全
局内存中的同一地址。这三个操作由一个原子操作执行。函数返回old
*/

int atomicAdd(int* address, int val);
unsigned int atomicAdd(unsigned int* address, unsigned int val);
/*
2. atomicSub()
从全局内存中读取地址为address 的32-bit 字old,计算(old - val),将结果返回全
局内存中的同一地址。这三个操作由一个原子操作执行。函数返回old
*/

int atomicSub(int* address, int val);
unsigned int atomicSub(unsigned int* address, unsigned int val);
/*
3 atomicExch()
从全局内存中读取地址为address 的32-bit 字old,存储val 返回全局内存中的同一地址。
这二个操作由一个原子操作执行。函数返回old。
*/

int atomicExch(int* address, int val);
unsigned int atomicExch(unsigned int* address, unsigned int val);
float atomicExch(float* address, float val);
/* 
4 atomicMin()
从全局内存中读取地址为address 的32-bit 字old,计算old 和val 的最小值,将结果返
回全局内存中的同一地址。这三个操作由一个原子操作执行。函数返回old。
*/

int atomicMin(int* address, int val);
unsigned int atomicMin(unsigned int* address,
unsigned int val);
/* 
5 atomicMax()
从全局内存中读取地址为address 的32-bit 字old,计算old 和val 的最大值,将结果返
回全局内存中的同一地址。这三个操作由一个原子操作执行。函数返回old
*/

int atomicMax(int* address, int val);
unsigned int atomicMax(unsigned int* address, unsigned int val);
/*
6 atomicInc()
从全局内存中读取地址为address 的32-bit 字old,计算((old >= val) ? 0 :
(old+1)),将结果返回全局内存中的同一地址。这三个操作由一个原子操作执行。函数返回old
*/
unsigned int atomicInc(unsigned int* address, unsigned int val);
/*
7 atomicDec()
从全局内存中读取地址为address的32-bit 字old,计算(((old ==0)| (old > val)) ?
val : (old-1)), 将结果返回全局内存中的同一地址。这三个操作由一个原子操作执行。
函数返回old。
*/
unsigned int atomicDec(unsigned int* address, unsigned int val);
/*
8 atomicCAS()
从全局内存中读取地址为address 的32-bit 字old,计算(old == compare ? val :
old),将结果返回全局内存中的同一地址。这三个操作由一个原子操作执行。函数返回old
(比较和置换)
*/
int atomicCAS(int* address, int compare, int val);
unsigned int atomicCAS(unsignedint*address, unsigned int compare, unsigned int val);
/* 
9. atomicAnd()
从全局内存中读取地址为address 的32-bit 字old,计算(old & val),将结果返回全
局内存中的同一地址。这三个操作由一个原子操作执行。函数返回old。
*/
int atomicAnd(int* address, int val);
unsigned int atomicAnd(unsigned int* address, unsigned int val);
/* 10. 
atomicOr()
从全局内存中读取地址为address 的32-bit 字old,计算(old | val),将结果返回全
局内存中的同一地址。这三个操作由一个原子操作执行。函数返回old。
*/ 
int atomicOr(int* address, int val);
unsigned int atomicOr(unsigned int* address, unsigned int val);
/* 
11 atomicXor()
从全局内存中读取地址为address 的32-bit 字old,计算(old ^ val),将结果返回全
局内存中的同一地址。这三个操作由一个原子操作执行。函数返回old
*/

int atomicXor(int* address, int val);
unsigned int atomicXor(unsigned int* address, unsigned int val);

32-bitUna función atómica realiza una operación atómica de lectura, modificación y escritura en una palabra en la memoria global.

Por ejemplo, atomicAdd()lea una palabra en la misma dirección en la memoria global 32-bit, agréguele un número entero y escriba el resultado nuevamente en la misma dirección.

El llamado "atómico" es para garantizar que la operación no interfiera con otros subprocesos. Ningún otro hilo puede acceder a esta dirección hasta que se complete la operación.

Las operaciones atómicas sólo están disponibles 32-biten enteros con y sin signo.


Componentes de tiempo de ejecución del host

Los componentes del host Runtimesolo pueden ser utilizados por funciones del host

Proporciona funciones para manejar los siguientes problemas:

  • gestión de dispositivos
  • Gestión del contexto
  • gestión de la memoria
  • Gestión del módulo de codificación
  • control ejecutivo
  • Gestión de referencias de textura.
  • Interoperabilidad de OpenGL y Direct3D

Se compone de dos API:

Un controlador APIde llamadas de bajo nivelCUDAAPI

APIUna llamada de alto nivel que se ejecuta por encima del CUDA runtime APIcontrolador CUDA.APIAPI

Estos APIson mutuamente excluyentes: una aplicación debe elegir uno de ellos para utilizarlo.

CUDA runtimecontextFacilita la gestión de códigos de dispositivos al proporcionar inicialización, gestión y gestión de módulos inherentes.

NvccEl código de host generado Cse basa en CUDA runtime, por lo que las aplicaciones que se conectan a este código deben usarCUDA runtime API

Por el contrario, CUDAun controlador APIrequiere más código, lo que dificulta la programación y la depuración, pero proporciona un mejor control y es
independiente del lenguaje ya que solo trata con cubinobjetos .

Especialmente con la configuración y el inicio CUDAdel controlador , es más difícil porque la configuración y los parámetros de ejecución deben especificar llamadas a funciones extrínsecas, reemplazando la sintaxis de configuración de ejecución.APIKernelkernel

Asimismo, la emulación de dispositivos no funciona con CUDAcontroladores.API

CUDAEl controlador se proporciona APIa través de cudauna biblioteca dinámica, todos sus puntos de entrada tienen el prefijocu

CUDA runtime APIProporcionado a través de cudartuna biblioteca dinámica, todos sus puntos de entrada tienen el prefijocuda


concepto publico

equipo

Ambos APIproporcionan funciones para enumerar los dispositivos disponibles en el sistema, consultar sus atributos y seleccionar uno de ellos para ejecutar.kernel

Algunos subprocesos de host pueden ejecutar código de dispositivo en el mismo dispositivo, pero por diseño, un subproceso de host solo puede ejecutar código de dispositivo en un dispositivo.

Por lo tanto, los subprocesos de múltiples hosts deben ejecutar el código del dispositivo en múltiples dispositivos.

runtimeAdemás, cualquier archivo fuente creado en un subproceso del host a través de CUDAno puede ser utilizado por otros subprocesos del host.

Memoria

La memoria del dispositivo se puede asignar en memoria lineal o como CUDAuna matriz

La memoria lineal del dispositivo utiliza 32-bitespacios de direcciones, de modo que las entidades asignadas por separado puedan referirse entre sí mediante punteros.

Por ejemplo, en una estructura de árbol binario, CUDAlas matrices son diseños de memoria opaca optimizados para la recuperación de texturas. Están compuestos por elementos unidimensionales o bidimensionales, cada uno con 1, 2 o 4 componentes, cada uno de los cuales puede estar firmado o no firmado 8- bit, entero de 16 bits o 32 bits, punto flotante de 16 bits (solo compatible con el controlador CUDA) o punto flotante de 32 bits.
API

CUDALas matrices sólo kernelse pueden leer a través de texturas.

Tanto la memoria lineal como CUDAlas matrices se pueden leer y escribir mediante la función de copia de memoria del host.

A diferencia de la memoria del host malloc()asignada por funciones , el host también proporciona funciones que pueden asignar y liberar memoria del host.pageableruntimepage-locked

Si la memoria del host se asigna comopage-locked

La ventaja de usar page-lockedmemoria es que el ancho de banda entre la memoria del host y la memoria del dispositivo será muy alto.

Sin embargo, asignar demasiada page-lockedmemoria reducirá la cantidad de memoria física disponible para el sistema, lo que reducirá el rendimiento general del sistema.

OpenGL Interoperability

Los objetos de búfer OpenGL se pueden asignar al espacio de direcciones CUDA, lo que permite a CUDA leer datos escritos por OpenGL.

O habilite CUDA para escribir datos consumidos por OpenGL

Direct3D Interoperability

Los buffers de vértices de Direct3D 9.0 se pueden asignar al espacio de direcciones CUDA, lo que permite a CUDA leer datos escritos por Direct3D.

O habilite CUDA para escribir datos consumidos por Direct3D

Un contexto CUDA solo puede usar un dispositivo Direct3D cada vez, al incluir la función de inicio/fin para llamar

El contexto CUDA y el dispositivo Direct3D deben estar integrados en la misma GPU

Esto se puede garantizar consultando el adaptador Direct3D asociado con el dispositivo CUDA.

Los dispositivos Direct3D deben crearse con el indicador D3DCREATE_HARDWARE_VERTEXPROCESSING

CUDA aún no admite:

  1. Versiones distintas a Direct3D 9.0
  2. Objetos Direct3D distintos de los buffers de vértices
  3. cudaD3D9GetDevice() y cuD3D9GetDevice() también pueden garantizar que el dispositivo Direct3D y el contexto CUDA se establezcan en diferentes dispositivos, como el equilibrio de carga de Direct3D y la sobreinteroperabilidad de CUDA.

Ejecución concurrente asincrónica

Para facilitar la ejecución simultánea entre el host y el dispositivo, algunas funciones de ejecución son asincrónicas: el control se devuelve a la aplicación hasta que el dispositivo haya completado la tarea solicitada.

  • El kernel se inicia mediante la función __global__ o cuGridLaunch() y cuGridLaunchAsync()
  • Las funciones que realizan copia de memoria requieren el sufijo Async
  • Funciones que realizan una copia de memoria de dispositivo a dispositivo
  • función para configurar la memoria

Algunos dispositivos también pueden realizar copias simultáneas entre la memoria del host con página bloqueada y la memoria del dispositivo.

La aplicación puede consultar si esta función está disponible a través de cuDeviceGetAttribute() junto con CU_DEVICE_ATTRIBUTE_GPU_OVERLAP

Actualmente, esta función solo admite la copia de memoria y se asigna mediante cudaMallocPitch() o cuMemAllocPitch(), excluyendo matrices CUDA o matrices 2D.

Las aplicaciones gestionan la simultaneidad a través de transmisiones. Una secuencia es una secuencia de operaciones ejecutadas en orden. Por otro lado, diferentes flujos pueden seguir ejecuciones paralelas o fuera de orden.

Una secuencia se define creando un objeto de secuencia y especificando sus parámetros de secuencia en la secuencia de inicio del kernel y la copia de memoria del host al dispositivo.

Solo si se han completado todas las operaciones: la operación incluida debe ser parte del flujo y ninguna operación posterior se completará hasta que se complete

Una secuencia con cero parámetros solo se inicia, por ejemplo: cualquier inicio del kernel, configuración de memoria o copia de memoria.

cudaStreamQuery() y cuStreamQuery() proporcionan un método para que la aplicación consulte si todas las operaciones en una secuencia se han completado

cudaStreamSynchronize() y cuStreamSynchronize() proporcionan un método para forzar a la aplicación a esperar hasta que se completen todas las operaciones en la secuencia.

Del mismo modo, las aplicaciones cudaThreadSynchronize() y cuThreadSynchronize() pueden forzar el tiempo de ejecución a esperar hasta que se completen todas las tareas del dispositivo.

Para evitar ralentizaciones, estas funciones se utilizan mejor con fines de sincronización, inicios aislados o fallas en la copia de memoria.

El tiempo de ejecución también proporciona un método para monitorear más de cerca el proceso del dispositivo.

En el momento preciso, permita que la aplicación registre eventos de forma asincrónica en cualquier punto del programa y consulte cuándo se registraron estos eventos.

Dos operaciones en diferentes flujos no se pueden ejecutar simultáneamente

Ya sea asignación de memoria de host bloqueada por página, asignación de memoria de dispositivo, configuración de memoria de dispositivo, copia de memoria de dispositivo a dispositivo o registro de eventos entre ellos

La ejecución asincrónica se puede desactivar globalmente estableciendo la variable de entorno CUDA_LAUNCH_BLOCKING en 1, para todas las aplicaciones CUDA en el sistema.

Esta función es sólo para fines de depuración, no para aumentar la confiabilidad de los productos de software.


API de tiempo de ejecución

[inicialización]

No existe una función de inicialización explícita para RuntimeAPI; se inicializa en la primera llamada a la función Runtime

Cabe señalar cuándo llamar a la función Runtime de manera oportuna y cuándo explicar el código de error que ingresó a Runtime desde la primera llamada.

[Gestión de dispositivos]

cudaGetDeviceCount()y cudaGetDeviceProperties()proporcionar un método para enumerar estos dispositivos y obtener sus propiedades

int deviceCount;
cudaGetDeviceCount(&deviceCount);
Int device;
for (device = 0; device < deviceCount; ++device) {
    
    
	cudaDeviceProp deviceProp;
	cudaGetDeviceProperties(&deviceProp,device);
}

cudaSetDevice()Se utiliza para seleccionar el dispositivo asociado con el hilo del host.

cudaSetDevice(device); 

Se debe seleccionar un dispositivo antes de que se llame a cualquier función __global__

De lo contrario, el dispositivo 0 se selecciona automáticamente y todas las selecciones de dispositivos posteriores no serán válidas.

[gestión de la memoria]

Llame a funciones para asignar y liberar memoria del dispositivo, acceder a la memoria asignada por variables declaradas arbitrariamente en la memoria global y transferir datos desde la memoria del host a la memoria del dispositivo.

La memoria lineal se asigna mediante cudaMalloc()o y se libera cudaMallocPitch()mediantecudaFree()

El siguiente código demuestra la asignación de una matriz de 256 elementos de punto flotante en la memoria lineal.

float* devPtr;
cudaMalloc((void**)&devPtr, 256 * sizeof(float)); 

Se recomienda asignar matrices 2D cudaMallocPitch()para garantizar el mejor rendimiento para acceder a direcciones de fila o copiar matrices 2D a otras áreas de la memoria del dispositivo.

El valor devuelto pitchdebe usarse para acceder a los elementos de la matriz.

El siguiente código demuestra la asignación de una matriz 2D de ancho x alto con flotantes y cómo recorrer los elementos de la matriz en el código del dispositivo:

// host code
float* devPtr;
int pitch;
cudaMallocPitch((void**)&devPtr, &pitch,
width*sizeof(float),height);
myKernel<<<100,512>>>(devPtr,pitch);
// device code
__global__ void myKernel(float* devPtr, int pitch)
{
    
    
	for (int r = 0; r < height; ++r) {
    
    
		float* row = (float*)((char*)devPtr + r * pitch);
		for (int c = 0; c < width; ++c) {
    
    
			float element = row[c];
		 }
	}
} 

Las matrices CUDA se asignan mediante cudaMallocArray()asignación, mediante cudaFreeArray()desasignación

cudaMallocArray()Se requiere una interpretación del formato, cudaCreateChannelDesc()establecida por

El siguiente código demuestra la asignación de una matriz 2D de ancho x alto con un número de punto flotante de 32 bits:

cudaChannelFormatDescchannelDesc=
cudaCreateChannelDesc<float>();
cudaArray* cuArray;
cudaMallocArray(&cuArray, &channelDesc, width, height);

cudaGetSymbolAddress()Se utiliza para obtener la dirección de memoria asignada a una variable declarada en la memoria global.

El tamaño de la memoria asignada se cudaGetSymbolSize()obtiene con

cudaMalloc()Memoria lineal asignada
cudaMallocPitch()Memoria lineal asignada, matriz CUDA, memoria asignada de variable global o memoria residente

El siguiente código demuestra cómo copiar una matriz 2D a la matriz CUDA asignada en el ejemplo anterior:

cudaMemcpy2DToArray(cuArray, 0, 0, devPtr, pitch,
width * sizeof(float),height,
cudaMemcpyDeviceToDevice);

El siguiente código demuestra cómo copiar algunas matrices de memoria del host a la memoria del dispositivo:

float data[256];
int size = sizeof(data);
float* devPtr;
cudaMalloc((void**)&devPtr, size);
cudaMemcpy(devPtr, data, size, cudaMemcpyHostToDevice);

El siguiente código demuestra cómo copiar algunas matrices de memoria del host a la memoria residente:

__constant__ float constData[256];
float data[256];
cudaMemcpyToSymbol(constData, data, sizeof(data));

[Gestión de secuencias]
Llame a la función para crear y destruir secuencias y determinar si todas las operaciones en una secuencia se han completado

El siguiente código demuestra que se crean dos secuencias:

cudaStream_t stream[2];
for(inti=0;i<2;++i)
cudaStreamCreate(&stream[i]);

El siguiente código demuestra que cada flujo se define y ejecuta una vez: copia de memoria del host al dispositivo, kernelinicio, copia de memoria del dispositivo al host.

for (int i = 0; i < 2; ++i)
	cudaMemcpyAsync(inputDevPtr+i*size,hostPtr+i*size,size,cudaMemcpyHostToDevice,stream[i]);
for (int i = 0; i < 2; ++i)
	myKernel<<<100, 512, 0, stream[i]>>>(outputDevPtr + i * size, inputDevPtr + i * size, size);
for (int i = 0; i < 2; ++i)
	cudaMemcpyAsync(hostPtr + i * size, outputDevPtr + i * size, size, cudaMemcpyDeviceToHost, stream[i]);
cudaThreadSynchronize();

Cada flujo copia parte de la matriz de entrada hostPtra la matriz de entrada en la memoria del dispositivoinputDevPtr

myKernel()Procesado en el dispositivo llamando inputDevPtry copiando el resultado outputDevPtren hostPtrla misma sección de

El manejo con dos flujos hostPtrpermite superponer una copia de memoria de un flujo a otro. Para cualquier superposición, hostPtrdebe apuntar a page-lockedla memoria del host:

float*hostPtr;
cudaMallocHost((void**)&hostPtr,2*size;

cudaThreadSynchronize()Se llama al final para garantizar que todas las transmisiones estén completas antes de continuar con el procesamiento.

[Gestión de eventos]

La función de llamada se utiliza para crear, registrar y destruir eventos y puede consultar el tiempo transcurrido entre dos eventos.

El siguiente código demuestra que se crean dos eventos:

cudaEvent_tstart,sto;
cudaEventCreate(&stat);
cudaEventCreate(&stop);

Estos eventos se pueden utilizar para cronometrar el código anterior:

cudaEventRecord(start, 0);
for (int i = 0; i < 2; ++i)
	cudaMemcpyAsync(inputDev + i * size, inputHost + i * size, size, cudaMemcpyHostToDevice, stream[i]);
for (int i = 0; i < 2; ++i)
	myKernel<<<100, 512, 0, stream[i]>>> (outputDev + i * size, inputDev + i * size, size);
for (int i = 0; i < 2; ++i)
	cudaMemcpyAsync(outputHost + i * size, outputDev + i * size, size, cudaMemcpyDeviceToHost, stream[i]);
cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);
float elapsedTime;
cudaEventElapsedTime(&elapsedTime, start, stop);

[Gestión de referencias de textura]

función de llamada para gestionartexture reference

Los tipos de textura son estructuras expuestas definidas por la API de alto nivel y texture Referencelos tipos están definidos por la API de bajo nivel, por ejemplo:

struct textureReference
{
    
    
int normalized;
enum cudaTextureFilterMode filterMode;
enum cudaTextureAddressMode addressMode[2];
struct cudaChannelFormatDesc channelDesc;
}

normalizedEspecifica si las coordenadas de textura son normalized;

Si es distinto de cero, todos los elementos de la textura tienen coordenadas de textura [0,1]en lugar de [0,width-1]o [0,height-1], donde widthy heightson el tamaño de la textura;

filterModeEspecifique el modo de filtro: según las coordenadas de textura de entrada, calcule cómo se devuelve el valor seleccionado por la textura;

modo de filtro es igual cudaFilterModePointa o cudaFilterModeLinear;

Si es así cudaFilterModePoint, el valor devuelto es igual al más cercano a las coordenadas de textura de entrada texel;

Si es así , el valor devuelto es igual al resultado de la interpolación lineal de dos (para texturas 1D) o cuatro (para texturas 2D) de cudaFilterModeLinearlas coordenadas de textura de entrada más cercanas ;texeltexel

cudaFilterModeLinearEl valor de retorno debe ser de tipo punto flotante;

addressModeEspecifique el modo de direccionamiento: si controla coordenadas de textura fuera de rango, especifique el modo de direccionamiento de la primera coordenada de textura y el modo de direccionamiento de la segunda coordenada de textura respectivamente;

el modo de direccionamiento es igual a cudaAddressModeClampo cudaAddressModeWrap;

En caso afirmativo cudaAddressModeClamp, las coordenadas de textura fuera de los límites se sujetan a los límites legales;

En caso afirmativo cudaAddressModeWrap, las coordenadas de textura que están fuera de los límites se sobrescriben según los límites legales;

cudaAddressModeWrapSólo normalizedse admiten coordenadas de textura;

channelDescDefine el formato del valor de retorno al seleccionar la textura; consulte el siguiente código:

struct cudaChannelFormatDesc {
    
    
	int x, y, z, w;
	enum cudaChannelFormatKind f;
};

en

x,y,zy wson el número de bits en cada parte del valor de retorno

fSí:

  • cudaChannelFormatKindSignedSi estas partes son números enteros con signo,
  • cudaChannelFormatKindUnsignedSi son enteros sin signo,
  • cudaChannelFormatKindFloatsi son flotadores.

normalized,addressMode,y filterModese puede modificar directamente en el código host.

Sólo se aplican a la matriz CUDA enlazadatexture reference

Antes de que un kernel texture referencelea la memoria de textura a través de

texture referenceDebe estar vinculado a una textura usando cudaBindTexture()o .cudaBindTextureToArray()

El siguiente código demuestra cómo vincular texture referencea devPtrla memoria lineal señalada por:

utilizar API de bajo nivel

texture<float, 1, cudaReadModeElementType> texRef;
textureReference* texRefPtr;
cudaGetTextureReference(&texRefPtr, “texRef”);
cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc<float>();
cudaBindTexture(0, texRefPtr, devPtr, &channelDesc, size);

Utilice API de alto nivel

texture<float, 1, cudaReadModeElementType> texRef;
cudaBindTexture(0, texRef, devPtr, size);

El siguiente código demuestra cómo vincular a texture referencea la matriz CUDA cuArray:

Utilice la API de bajo nivel:

texture<float, 2, cudaReadModeElementType> texRef;
textureReference* texRefPtr;
cudaGetTextureReference(&texRefPtr, “texRef”);
cudaChannelFormatDesc channelDesc;
cudaGetChannelDesc(&channelDesc, cuArray);
cudaBindTextureToArray(texRef, cuArray, &channelDesc);

Usando API de alto nivel:

texture<float, 2, cudaReadModeElementType> texRef;
cudaBindTextureToArray(texRef, cuArray);

El formato para vincular una textura a la referencia de textura debe coincidir con los parámetros al declarar la referencia de textura;

De lo contrario, el resultado de la búsqueda de texturas no estará definido.

cudaUnbindTexture()Se utiliza para desinstalar texture referenceenlaces para .

[Interoperabilidad OpenGL]

Función de llamada para controlar la interoperabilidad de OpenGL

Un objeto de búfer debe registrarse con CUDA antes de que pueda asignarse

uso cudaGLRegisterBufferObject():

GLuint bufferObj;
cudaGLRegisterBufferObject(bufferObj);

Después del registro, el objeto del búfer se puede kenrelleer o escribir utilizando la memoria del dispositivo y la dirección del dispositivo de memoria se cudaGLMapBufferObject()devuelve mediante:

GLuint bufferObj;
float* devPtr;
cudaGLMapBufferObject((void**)&devPtr, bufferObj);

descargar el mapeo y registro vía, cudaGLUnmapBufferObject()ycudaGLUnregisterBufferObject()

[Interoperabilidad Direct3D]

Función de llamada para controlar la interoperabilidad de Direct3D

Direct3DLa interoperabilidad debe cudaD3D9Begin()iniciarse cudaD3D9End()y finalizarse.

Un objeto de vértice debe registrarse con CUDA antes de poder mapearlo. uso cudaD3D9RegisterVertexBuffer():

LPDIRECT3DVERTEXBUFFER9 vertexBuffer;
cudaD3D9RegisterVertexBuffer(vertexBuffer);

Después del registro, el objeto del búfer se puede leer o escribir usando la memoria del dispositivo a través de kenrel, y la dirección del dispositivo de memoria se cudaD3D9MapVertexBuffer()devuelve mediante:

LPDIRECT3DVERTEXBUFFER9 vertexBuffer;
float* devPtr;
cudaD3D9MapVertexBuffer((void**)&devPtr, vertexBuffer);

descargar el mapeo y registro vía, cudaD3D9UnmapVertexBuffer()ycudaD3D9UnregisterVertexBuffer()

[Utilice la emulación de dispositivo para depurar]

El entorno de programación no admite ninguna depuración nativa del código que se ejecuta en el dispositivo, pero viene con un modo de emulación de dispositivo para fines de depuración.

Al compilar una aplicación en este modo (usando la deviceemuopción -), el código del dispositivo se compila para

Permite a los desarrolladores depurar aplicaciones utilizando el soporte de depuración nativo del host.

Las macros del preprocesador __DEVICE_EMULATION__se definen en este modo.

Todo el código de la aplicación actual, incluidas las bibliotecas utilizadas, debe compilarse, ya sea para la emulación o ejecución del dispositivo.

Los errores de tiempo de ejecución generados por la emulación del dispositivo conectado o el código ejecutado por el dispositivo pueden cudaErrorMixedDeviceExxcutiondevolverse mediante .

Cuando se ejecuta una aplicación en modo de emulación de dispositivo, el tiempo de ejecución emula el modelo de programación.

Para cada hilo en el bloque de hilos, runtimese genera un hilo en el host. Los desarrolladores deben determinar:

La cantidad máxima de subprocesos que el host puede ejecutar por bloque, más un subproceso principal

Hay suficiente memoria disponible para ejecutar todos los subprocesos, cada subproceso requiere 256 KBuna pila

Varias funciones disponibles a través del modo de emulación de dispositivo lo convierten en un conjunto de herramientas de depuración muy eficaz:

Al utilizar el soporte de depuración nativo del host, los desarrolladores pueden utilizar todas las funciones del depurador, como establecer puntos de interrupción e inspección de datos.

Dado que el código del dispositivo se compila para ejecutarse en el host, este código se puede complementar con código que no se puede ejecutar en el dispositivo.

Como printf()operaciones de entrada y salida a archivos o pantallas (, etc.)

Dado que todos los datos residen en el host, cualquier dato específico del dispositivo o del host se puede leer desde el código del dispositivo o del host;

Del mismo modo, se puede llamar a cualquier dispositivo o función de host desde el dispositivo o el código de host.

Para evitar el uso incorrecto de la sincronización, runtimese monitorean las condiciones de interbloqueo.

Los desarrolladores deben tener en cuenta que el modo de emulación de dispositivo imita al dispositivo, no lo emula.

Por lo tanto, el modo de emulación de dispositivo es muy útil para encontrar errores algorítmicos, pero algunos errores son difíciles de encontrar.

Cuando varios subprocesos dentro de la cuadrícula acceden a las celdas de memoria al mismo tiempo, los resultados cuando se ejecuta en modo de emulación de dispositivo son bastante diferentes de los de ejecución en el dispositivo, porque los subprocesos se ejecutan secuencialmente en modo de emulación.

Al eliminar la referencia a una referencia a la memoria global en el host o a la memoria del host en el dispositivo, es casi seguro que la ejecución del dispositivo fallará de alguna manera indefinida, mientras que la emulación del dispositivo puede producir resultados correctos.

En la mayoría de los casos, el mismo cálculo de punto flotante no producirá exactamente los mismos resultados cuando se realiza en el dispositivo que en el host a través del modo de emulación del dispositivo.

Por lo general, esto es de esperar: para el mismo cálculo de punto flotante, los diferentes resultados obtenidos se forman mediante diferentes opciones del compilador, sin mencionar diferentes compiladores, diferentes conjuntos de instrucciones o diferentes arquitecturas.

En particular, algunas plataformas host almacenan resultados intermedios de cálculos de punto flotante de precisión simple en registros de precisión extendida, lo que a menudo resulta en una gran diferencia en la precisión en el modo de emulación del dispositivo.

Cuando surgen estas situaciones, los desarrolladores pueden probar lo siguiente, ninguno de los cuales está totalmente garantizado que funcione:

  1. Declare algunas variables de punto flotante para forzar el almacenamiento de precisión simple debido a la inestabilidad;
  2. Utilice opciones gccdel compilador ;–ffloat-storegcc
  3. Utilice la opción o Visual C++del compilador ;/Op/fp
  4. para Linuxusar _FPU_GETCW()y _FPU_SETCW()para Windowsusar_controlfp()

función para forzar una parte del código a realizar cálculos de punto flotante de precisión simple

unsigned int originalCW;
_FPU_GETCW(originalCW);
unsigned int cw = (originalCW & ~0x300) | 0x000;
_FPU_SETCW(cw);
unsigned int originalCW = _controlfp(0, 0);
_controlfp(_PC_24, _MCW_PC);

Al principio, almacene el valor actual de la palabra de control y fuerce el almacenamiento de la mantisa en 24 bits _FPU_SETCW(originalCW);o_controlfp(originalCW, 0xfffff);

A diferencia de los dispositivos informáticos, las plataformas host también suelen admitir valores numéricos normalizados.


Esto puede generar resultados significativamente diferentes entre los modos de emulación y ejecución de dispositivo, ya que algunos cálculos pueden producir un resultado finito en un caso y un resultado infinito en el otro.


API del controlador

Los controladores APIse basan en identificadores y son imperativos API: se hace referencia a la mayoría de los objetos a través de identificadores opacos.

Los objetos disponibles CUDAse resumen en la Tabla 4-1.
inserte la descripción de la imagen aquí

[inicialización]

cuInit()Se requiere la inicialización de la función antes de llamar a otras funciones .

[Gestión de dispositivos]

Llame a la función para administrar los dispositivos en el sistema actual.

cuDeviceGetCount()y cuDeviceGet()se utiliza para enumerar estos dispositivos

int deviceCount;
cuDeviceGetCount(&deviceCount);
int device;
for (int device = 0; device < deviceCount; ++device) {
    
    
CUdevice cuDevice;
cuDeviceGet(&cuDevice, device)
int major, minor;
cuDeviceComputeCapability(&major, &minor, cuDevice);
}

[Gestión del contexto]

Llamar a funciones para crear, agrupar y separarCUDA context

A CUDA contextes similar a un CPUmango

Todos los recursos y acciones realizadas dentro de Compute API se encapsulan en CUDA contexty contextel sistema limpia automáticamente estos recursos cuando se destruyen.

contextCada uno tiene su propio 32bitespacio de direcciones independiente , excepto objetos como referencias de módulos y texturas.

Por lo tanto, los valores de diferentes CUDA contextse refieren CUdeviceptra diferentes ubicaciones de memoria.

ContextHay un mecanismo de correspondencia uno a uno en el hilo del host.

Un hilo host solo puede tener un dispositivo a la vezcontext

contextCuando se crea a cuCtxCreate(), se convierte en el hilo del host que llama actualmente.

contextLas funciones que operan en CUDA(la mayoría de las funciones excluyendo la enumeración o contextadministración de dispositivos) regresarán CUDA_ERROR_INVALID_CONTEXTsi el hilo actual no es válidocontext

Para facilitar contextla interoperabilidad entre el código con licencia de terceros que se ejecuta en el mismo, el controlador proporciona un contador de uso APIproporcionado por cada cliente definido.context

Por ejemplo, si se cargan tres bibliotecas usando la misma CUDA context, cada biblioteca debe llamar cuCtxAttach()para incrementar el contador de uso, y cuando la biblioteca haya terminado contextde usar, llamar para cuCtxDetach()disminuir el contador de uso.

contextDestruido cuando el contador de uso llega a 0

Para la mayoría de las bibliotecas, la aplicación debe crear una antes de cargar o inicializar la biblioteca CUDA context;

De esta manera, la aplicación puede crear una biblioteca para su propio uso contexty la biblioteca simplemente opera contexten las tareas que se le han confiado.

[Gestión de módulos]

La función de llamada se utiliza para cargar y descargar el módulo, y obtener el identificador en el módulo, el puntero de la variable o la definición de la función.

Los módulos son paquetes tar que se pueden cargar dinámicamente y que contienen código y datos del dispositivo, como Windowsen , que se exportan DLLa través denvcc

Los nombres de todos los indicadores, incluidas funciones, variables globales y referencias de textura, se proporcionan en el alcance del módulo para que los módulos escritos por terceros independientes en el CUDA contextmismo

El siguiente código demuestra cómo cargar un módulo y kernelmanejarlo:

CUmodule cuModule;
cuModuleLoad(&cuModule, "myModule.cubin");
CUfunction cuFunction;
cuModuleGetFunction(&cuFunction, cuModule, “myKernel”);

[control de ejecución]

kernelFunción de llamada para gestionar la ejecución de un en el dispositivo.

cuFuncSetBlockShape()Se utiliza para establecer el número de subprocesos en cada bloque de una función determinada y cómo se asignan los ID de los subprocesos.

cuFuncSetSharedSize()Especifica el tamaño de la memoria compartida de la función.

cuParam*()El conjunto de funciones proporciona parámetros para que el kernel kernelincluya culanuchGrid()ocuLanuch()

cuFuncSetBlockShape(cuFunction, blockWidth, blockHeight, 1);
int offset = 0; int i;
cuParamSeti(cuFunction, offset, i);
Offset += sizeof(i);
float f;
cuParamSetf(cuFunction, offset, f);
offset += sizeof(f);
char data[32];
cuParamSetv(cuFunction, offset, (void*)data, sizeof(data));
offset += sizeof(data);
cuParamSetSize(cuFunction, offset);
cuFuncSetSharedSize(cuFunction, numElements * sizeof(float));
cuLaunchGrid(cuFuntion, gridWidth, gridHeight);

[gestión de la memoria]

Se llaman funciones para asignar y liberar memoria del dispositivo y para transferir datos entre la memoria del host y del dispositivo.

La memoria lineal es asignada por cuMemAlloc()o para liberar.cuMemAllocPitch()cuMemFree()

El siguiente código demuestra la asignación de una matriz de 256 elementos de punto flotante en la memoria lineal:

CUdeviceptr devPtr;
cuMemAlloc(&devPtr, 256 * sizeof(float));

Se recomienda asignar 2Dmatrices cuMemMallocPitch()para garantizar 2Del mejor rendimiento al acceder a direcciones de fila o copiar matrices a otras áreas de la memoria del dispositivo.

El valor devuelto pitchdebe usarse para acceder a los elementos de la matriz.

El siguiente código demuestra cómo asignar una matriz 2D de ancho x alto con flotantes y cómo recorrer los elementos de la matriz en el código del dispositivo:

// host code
CUdeviceptr devPtr;
int pitch;
cuMemAllocPitch(&devPtr, &pitch, width * sizeof(float), height, 4);
cuModuleGetFunction(&cuFunction,cuModule,"myKernel");
cuFuncSetBlockShape(cuFunction, 512, 1, 1);
cuParamSeti(cuFunction, 0, devPtr);
cuParamSetSize(cuFunction, sizeof(devPtr));
cuLaunchGrid(cuFunction, 100, 1);
// device code
__global__ void myKernel(float* devPtr)
{
    
    
	for (int r = 0; r < height; ++r) {
    
    
		float* row = (float*)((char*)devPtr + r * pitch);
		for (int c = 0; c < width; ++c) {
    
    
			float element = row[c];
		}
	}
}

CUDALas matrices son cuArrayCreate()asignadas por, cuArrayDestroy()liberadas por

El siguiente código demuestra la asignación de una matriz de ancho x alto con un 32-bitflotante CUDA:

CUDA_ARRAY_DESCRIPTOR desc;
desc.Format = CU_AD_T_FLOAT;
desc.NumChannels = 1;
desc.Width = width;
desc.Height = height;
CUarray cuArray;
cuArrayCreate(&cuArray, &desc);

El siguiente código demuestra cómo copiar una matriz 2D a la matriz CUDA asignada en el ejemplo anterior:

CUDA_MEMCPY2D copyParam;
memset(&copyParam, 0, sizeof(copyParam));
copyParam.dstMemoryType = CU_MEMORYTYPE_ARRAY;
copyParam.dstArray = cuArray;
copyParam.srcMemoryType = CU_MEMORYTYPE_DEVICE;
copyParam.srcDevice = devPtr;
copyParam.srcPitch = pitch;
copyParam.WidthInBytes = width * sizeof(float);
copyParam.Height = height;
cuMemcpy2D(&copyParam);

El siguiente código demuestra cómo copiar algunas matrices de memoria del host a la memoria del dispositivo:

float data[256];
int size = sizeof(data);
CUdeviceptr devPtr;
cuMemMalloc(&devPtr, size);
cuMemcpyHtoD(devPtr, data, size);

[Gestión de flujo]

La función de llamada se utiliza para crear y destruir secuencias y para determinar si todas las operaciones en una secuencia están completas.

El siguiente código demuestra que se crean dos secuencias:

CUStream stream[2];
for (int i = 0; i < 2; ++i)
cuStreamCreate(&stream[i], 0);

El siguiente código demuestra que cada flujo se define y ejecuta una vez: copia de memoria del host al dispositivo, inicio del kernel, copia de memoria del dispositivo al host

for (int i = 0; i < 2; ++i)
cuMemcpyHtoDAsync(inputDevPtr + i * size, hostPtr + i * size, size, stream[i]);
for (int i = 0; i < 2; ++i) {
    
    
	cuFuncSetBlockShape(cuFunction, 512, 1, 1);
	int offset = 0;
	cuParamSeti(cuFunction, offset, outputDevPtr);
	offset += sizeof(int);
	cuParamSeti(cuFunction, offset, inputDevPtr);
	offset += sizeof(int);
	cuParamSeti(cuFunction, offset, size);
	offset += sizeof(int);
	cuParamSetSize(cuFunction, offset);
}
cuLaunchGridAsync(cuFunction, 100, 1, stream[i]
for (int i = 0; i < 2; ++i)
	cuMemcpyDtoHAsync(hostPtr + i * size, outputDevPtr + i * size,
size, stream[i]);
cuCtxSynchronize();

Cada flujo copia parte de la matriz de entrada hostPtra la matriz de entrada en la memoria del dispositivo , se procesa en el dispositivo inputDevPtrllamando y copia el resultado en la misma parte de .cuFunctioninputDevPtroutputDevPtrhostPtr

El manejo con dos secuencias hostPtrpermite sobrescribir las copias de memoria de una secuencia a la otra.

Para cualquier anulación, hostPtrdebe apuntar a page-lockedla memoria del host en:

float* hostPtr;
cuMemMallocHost((void**)&hostPtr, 2 * size);

cuCtxSynchronize()Se llama al final para garantizar que todas las transmisiones estén completas antes de continuar con el procesamiento.

[Gestión de eventos]

La función de llamada se utiliza para crear, registrar y destruir eventos y puede consultar el tiempo transcurrido entre dos eventos.

El siguiente código demuestra que se crean dos eventos.

CUEvent start,
stop;cuEventCreate(&start);
cuEventCreate(&stop);

Estos eventos se pueden utilizar para cronometrar el código anterior:

cuEventRecord(start, 0);
for (int i = 0; i < 2; ++i)
	cuMemcpyHtoDAsync(inputDevPtr + i * size, hostPtr + i * size, size, stream[i]);
for (int i = 0; i < 2; ++i) {
    
    
	cuFuncSetBlockShape(cuFunction, 512, 1, 1);
	int offset = 0;
	cuParamSeti(cuFunction, offset, outputDevPtr);
	offset += sizeof(int);
	cuParamSeti(cuFunction, offset, inputDevPtr);
	offset += sizeof(int);
	cuParamSeti(cuFunction, offset, size);
	offset += sizeof(int);
	cuParamSetSize(cuFunction, offset);
}
cuLaunchGridAsync(cuFunction, 100, 1, stream[i]
for (int i = 0; i < 2; ++i)
	cuMemcpyDtoHAsync(hostPtr + i * size, outputDevPtr + i * size, size, stream[i]);
cuEventRecord(stop, 0);
cuEventSynchronize(stop);
float elapsedTime;
cuEventElapsedTime(&elapsedTime, start, stop);

[Gestión de referencias de textura]

función de llamada para gestionartexture reference

Antes de que un pase pueda leer una memoria kernelde textura texture reference, texture referencedebe estar vinculada a una textura cuTexRefSetAddress()o cuTexRefSetArray()usarse en una textura.

Si un módulo cuModulecontiene algunos texture reference texRefdefinidos comotexture<float, 2, cudaReadModeElementType> texRef;

El siguiente código demuestra que texRefse obtiene un identificador para:

CUtexref cuTexRef;
cuModuleGetTexRef(&cuTexRef, cuModule, “texRef”);

El siguiente código demuestra cómo vincular texture referencea devPtrla memoria lineal señalada por:

cuTexRefSetAddress(Null, cuTexRef, devPtr, size);

El siguiente código demuestra cómo vincular a texture referencea la matriz CUDA cuArray:

cuTexRefSetArray(cuTexRef, cuArrary, CU_TRSA_OVERRIDE_FORMAT);

Configure texture referenceel modo de direccionamiento, el modo de filtro, el formato y otros indicadores para

El formato al que se vincula una textura texture referencedebe coincidir con texture referencelos parámetros al declarar;

De lo contrario, el resultado de la búsqueda de textura no estará definido.

[Interoperabilidad OpenGL]

Función de llamada para controlar OpenGLla interoperabilidad de

OpenGLLa interoperabilidad debe ser cuGLInit()inicializada por

Un objeto de búfer debe registrarse con elCUDA

uso cuGLRegisterBufferObject():

GLuint bufferObj;
cuGLRegisterBufferObject(bufferObj);

Después del registro, el objeto del búfer se puede leer o escribir usando la memoria del dispositivo a través de kenrel, y la dirección del dispositivo de memoria se cuGLMapBufferObject()devuelve mediante:

GLuint bufferObj;
CUdeviceptr devPtr;
Int size;
cuGLMapBufferObject(&devPtr, &size, bufferObj);

Desinstale el mapa y regístrese a través de,cuGLUnmapBufferObject()和cuGLUnregisterBufferObject()

[Interoperabilidad Direct3D]

Función de llamada para controlar Direct3Dla interoperabilidad de

Direct3DLa interoperabilidad debe ser cuD3D9Begin()inicializada y cuD3D9End()finalizada por

Un objeto de vértice debe registrarse con CUDAelcuD3D9RegisterVertexBuffer()

LPDIRECT3DVERTEXBUFFER9 vertexBuffer;
cuD3D9RegisterVertexBuffer(vertexBuffer);

Después del registro, el objeto del búfer se puede leer o escribir usando la memoria del dispositivo a través de kenrel, y la dirección del dispositivo de memoria se cuD3D9MapVertexBuffer()devuelve mediante:

LPDIRECT3DVERTEXBUFFER9 vertexBuffer;
CUdeviceptr devPtr;
Int size;
cuD3D9MapVertexBuffer(&devPtr, &size, vertexBuffer);

Desinstale el mapa y regístrese a través de,cuD3D9UnmapVertexBuffer()和cuD3D9UnregisterVertexBuffer()

Supongo que te gusta

Origin blog.csdn.net/qq_38973721/article/details/132392024
Recomendado
Clasificación