aprendizaje de programación cuda - función atómica (10)

prefacio

Referencias:

El blog de Gao Sheng
"Guía autorizada de programación de CUDA C"
y el documento oficial de CUDA
Programación de CUDA: conceptos básicos y práctica Fan Zheyong

Todos los códigos del artículo están disponibles en mi GitHub y se actualizarán lentamente en el futuro.

Los artículos y videos explicativos se actualizan simultáneamente para el público "AI Knowledge Story", estación B: sal a comer tres tazones de arroz

1: función atómica

La función de la operación atómica se llama función atómica para abreviar.

En CUDA, la operación atómica de un subproceso puede completar un conjunto de operaciones de "lectura-modificación-escritura" en determinados datos (en la memoria global o memoria compartida) sin verse afectado por ninguna operación de otros subprocesos.
También se puede decir que el conjunto de operaciones es inseparable.

2: Funciones atómicas y cálculos de reducción

Para el cálculo de la reducción, consulte la introducción en el capítulo anterior (Capítulo 9).

Para el cálculo de reducción en los capítulos anteriores, la función kernel no hizo todos los cálculos, es decir, no ejecutó todos los cálculos en la GPU, sino que simplemente cambió una matriz más larga d_x en una matriz más corta d_y, cada una de las cuales La elemento es la suma de varios elementos del primero. Después de llamar a la función del kernel, la matriz más corta se copia en el host, donde se realiza el resto de la suma .

Hay dos formas de obtener el resultado final en la GPU,
una es usar otra función del kernel para reducir aún más la matriz más corta para obtener el resultado final (un valor);
la segunda es usar la función atómica al final de la anterior Reducción de la función del kernel, obtenga el resultado final directamente.

Este artículo analiza el segundo método.

//第9章归约核 函数的最后几行
//if 语句块的作用是将每一个线程块中归约的结果从共享内存 s_y[0] 复制到全
//局内 存d_y[bid]。为了将不同线程块的部分和s_y[0]累加起来,存放到一个全局
//内存地址
if (tid == 0)
{
    
    
d_y[bid] = s_y[0];
}

//使用原子函数

if (tid == 0) 
{
    
    
//第一个参数是待累加变量的地址address,第二个 参数是累加的值val
//该函数的作用是将地址address中的旧值old读出,计算old + val, 然后将计算的值存入地址address。
//这些操作在一次原子事务(atomic transaction)中完成,不会被别的线程中的原子操作所干扰。
atomicAdd(&d_y[0], s_y[0]);
}

La función atómica realiza una **operación atómica de "lectura-modificación-escritura"** en los datos a los que apunta su primer parámetro. El primer parámetro puede apuntar a la memoria global oa la memoria compartida. Para todos los hilos participantes, la operación atómica "leer-modificar-escribir" se realiza uno por uno, pero no hay un orden claro. Además, las funciones atómicas no tienen capacidades de sincronización.

prototipo de una función atómica
1. 加法:T atomicAdd(T *address, T val); 功能:new = old + val。
2. 减法:T atomicSub(T *address, T val); 功能:new = old - val。
3. 交换:T atomicExch(T *address, T val); 功能:new = val。
4. 最小值:T atomicMin(T *address, T val); 功能:new = (old < val) ? old : val。
5. 最大值:T atomicMax(T *address, T val);
功能:new = (old > val) ? old : val。
6. 自增:T atomicInc(T *address, T val); 功能:new = (old >= val) ? 0 : (old + 1)7. 自减:T atomicDec(T *address, T val); 功能:new = ((old == 0) || (old > val)) ? val : (old - 1)8. 比较-交换(Compare And Swap):T atomicCAS(T *address, T compare, T val); 功能:new = (old == compare) ? val : old。
9. 按位与:T atomicAnd(T *address, T val); 功能:new = old & val。
10. 按位或:T atomicOr(T *address, T val); 功能:new = old | val。
11. 按位异或:T atomicXor(T *address, T val);
功能:new = old ^ val。

inserte la descripción de la imagen aquí

Funciones atómicas: cálculos de reducción

#include<stdint.h>
#include<cuda.h>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
#include <math.h>
#include <stdio.h>

#define CHECK(call)                                   \
do                                                    \
{
      
                                                           \
    const cudaError_t error_code = call;              \
    if (error_code != cudaSuccess)                    \
    {
      
                                                       \
        printf("CUDA Error:\n");                      \
        printf("    File:       %s\n", __FILE__);     \
        printf("    Line:       %d\n", __LINE__);     \
        printf("    Error code: %d\n", error_code);   \
        printf("    Error text: %s\n",                \
            cudaGetErrorString(error_code));          \
        exit(1);                                      \
    }                                                 \
} while (0)

#ifdef USE_DP
typedef double real;
#else
typedef float real;
#endif

const int NUM_REPEATS = 100;
const int N = 100000000;
const int M = sizeof(real) * N;
const int BLOCK_SIZE = 128;

void timing(const real* d_x);

int main(void)
{
    
    
    real* h_x = (real*)malloc(M);
    for (int n = 0; n < N; ++n)
    {
    
    
        h_x[n] = 1.23;
    }
    real* d_x;
    CHECK(cudaMalloc(&d_x, M));
    CHECK(cudaMemcpy(d_x, h_x, M, cudaMemcpyHostToDevice));

    printf("\nusing atomicAdd:\n");
    timing(d_x);

    free(h_x);
    CHECK(cudaFree(d_x));
    return 0;
}

void __global__ reduce(const real* d_x, real* d_y, const int N)
{
    
    
    const int tid = threadIdx.x;
    const int bid = blockIdx.x;
    const int n = bid * blockDim.x + tid;
    extern __shared__ real s_y[];
    s_y[tid] = (n < N) ? d_x[n] : 0.0;
    __syncthreads();

    for (int offset = blockDim.x >> 1; offset > 0; offset >>= 1)
    {
    
    
        if (tid < offset)
        {
    
    
            s_y[tid] += s_y[tid + offset];
        }
        __syncthreads();
    }

    if (tid == 0)
    {
    
    
        atomicAdd(d_y, s_y[0]);
    }
}

real reduce(const real* d_x)
{
    
    
    const int grid_size = (N + BLOCK_SIZE - 1) / BLOCK_SIZE;
    const int smem = sizeof(real) * BLOCK_SIZE;

    real h_y[1] = {
    
     0 };
    real* d_y;
    CHECK(cudaMalloc(&d_y, sizeof(real)));
    CHECK(cudaMemcpy(d_y, h_y, sizeof(real), cudaMemcpyHostToDevice));

    reduce << <grid_size, BLOCK_SIZE, smem >> > (d_x, d_y, N);

    CHECK(cudaMemcpy(h_y, d_y, sizeof(real), cudaMemcpyDeviceToHost));
    CHECK(cudaFree(d_y));

    return h_y[0];
}

void timing(const real* d_x)
{
    
    
    real sum = 0;

    for (int repeat = 0; repeat < NUM_REPEATS; ++repeat)
    {
    
    
        cudaEvent_t start, stop;
        CHECK(cudaEventCreate(&start));
        CHECK(cudaEventCreate(&stop));
        CHECK(cudaEventRecord(start));
        cudaEventQuery(start);

        sum = reduce(d_x);

        CHECK(cudaEventRecord(stop));
        CHECK(cudaEventSynchronize(stop));
        float elapsed_time;
        CHECK(cudaEventElapsedTime(&elapsed_time, start, stop));
        printf("Time = %g ms.\n", elapsed_time);

        CHECK(cudaEventDestroy(start));
        CHECK(cudaEventDestroy(stop));
    }

    printf("sum = %f.\n", sum);
}



inserte la descripción de la imagen aquí

En comparación con el noveno artículo que usa memoria compartida de 29 ms, hay una ligera mejora en el rendimiento

Supongo que te gusta

Origin blog.csdn.net/qq_40514113/article/details/130994374
Recomendado
Clasificación