cuda入门

cuda入门

cuda c 代码分为主机代码和设备代码,cuda编译器和运行时负责从主机代码中调用设备代码。使用 __global__ 标识设备代码,使用尖括号传递如何启动设备代码得参数,代码得参数还是放在圆括号中。主机指针只能访问主机代码内存,设备指针只能访问设备代码内存。

  1. 设备指针可以传递给设备上执行的函数。
  2. 设备指针可以在设备代码中读写。
  3. 设备指针可以传递给主机函数。
  4. 主机代码不能读写设备指针。

cuda程序执行流程:

  1. 分配host内存,并进行数据初始化;
  2. 分配device内存,并从host将数据拷贝到device上;
  3. 调用CUDA的核函数在device上完成指定的运算;
  4. 将device上的运算结果拷贝到host上;
  5. 释放device和host上分配的内存。

上面流程中最重要的一个过程是调用CUDA的核函数来执行并行计算,kernel是CUDA中一个重要的概念,kernel是在device上线程中并行执行的函数,核函数用__global__符号声明,在调用时需要用<<<grid, block>>>来指定kernel要执行的线程数量,在CUDA中,每一个线程都要执行核函数,并且每个线程会分配一个唯一的线程号thread ID,这个ID值可以通过核函数的内置变量threadIdx来获得。

由于GPU实际上是异构模型,所以需要区分host和device上的代码,在CUDA中是通过函数类型限定词开区别host和device上的函数,主要的三个函数类型限定词如下:

  • __global__:在device上执行,从host中调用(一些特定的GPU也可以从device上调用),返回类型必须是void,不支持可变参数参数,不能成为类成员函数。注意用__global__定义的kernel是异步的,这意味着host不会等待kernel执行完就执行下一步。
  • __device__:在device上执行,单仅可以从device中调用,不可以和__global__同时用。
  • __host__:在host上执行,仅可以从host上调用,一般省略不写,不可以和__global__同时用,但可和__device__,此时函数会在device和host都编译。

要深刻理解kernel,必须要对kernel的线程层次结构有一个清晰的认识。首先GPU上很多并行化的轻量级线程。kernel在device上执行时实际上是启动很多线程,一个kernel所启动的所有线程称为一个网格(grid),同一个网格上的线程共享相同的全局内存空间,grid是线程结构的第一层次,而网格又可以分为很多线程块(block),一个线程块里面包含很多线程,这是第二个层次。线程两层组织结构如下图所示,这是一个gird和block均为2-dim的线程组织。grid和block都是定义为dim3类型的变量,dim3可以看成是包含三个无符号整数(x,y,z)成员的结构体变量,在定义时,缺省值初始化为1。因此grid和block可以灵活地定义为1-dim,2-dim以及3-dim结构,对于图中结构(主要水平方向为x轴),定义的grid和block如下所示,kernel在调用时也必须通过执行配置<<<grid, block>>>来指定kernel所使用的线程数及结构。

cuda 程序格式

设备信息描述结构体:

/**
 * CUDA device properties
 */
struct __device_builtin__ cudaDeviceProp
{
    char         name[256];                  /**< ASCII string identifying device */
    cudaUUID_t   uuid;                       /**< 16-byte unique identifier */
    char         luid[8];                    /**< 8-byte locally unique identifier. Value is undefined on TCC and non-Windows platforms */
    unsigned int luidDeviceNodeMask;         /**< LUID device node mask. Value is undefined on TCC and non-Windows platforms */
    size_t       totalGlobalMem;             /**< Global memory available on device in bytes */
    size_t       sharedMemPerBlock;          /**< Shared memory available per block in bytes */
    int          regsPerBlock;               /**< 32-bit registers available per block */
    int          warpSize;                   /**< Warp size in threads */
    size_t       memPitch;                   /**< Maximum pitch in bytes allowed by memory copies */
    int          maxThreadsPerBlock;         /**< Maximum number of threads per block */
    int          maxThreadsDim[3];           /**< Maximum size of each dimension of a block */
    int          maxGridSize[3];             /**< Maximum size of each dimension of a grid */
    int          clockRate;                  /**< Clock frequency in kilohertz */
    size_t       totalConstMem;              /**< Constant memory available on device in bytes */
    int          major;                      /**< Major compute capability */
    int          minor;                      /**< Minor compute capability */
    size_t       textureAlignment;           /**< Alignment requirement for textures */
    size_t       texturePitchAlignment;      /**< Pitch alignment requirement for texture references bound to pitched memory */
    int          deviceOverlap;              /**< Device can concurrently copy memory and execute a kernel. Deprecated. Use instead asyncEngineCount. */
    int          multiProcessorCount;        /**< Number of multiprocessors on device */
    int          kernelExecTimeoutEnabled;   /**< Specified whether there is a run time limit on kernels */
    int          integrated;                 /**< Device is integrated as opposed to discrete */
    int          canMapHostMemory;           /**< Device can map host memory with cudaHostAlloc/cudaHostGetDevicePointer */
    int          computeMode;                /**< Compute mode (See ::cudaComputeMode) */
    int          maxTexture1D;               /**< Maximum 1D texture size */
    int          maxTexture1DMipmap;         /**< Maximum 1D mipmapped texture size */
    int          maxTexture1DLinear;         /**< Deprecated, do not use. Use cudaDeviceGetTexture1DLinearMaxWidth() or cuDeviceGetTexture1DLinearMaxWidth() instead. */
    int          maxTexture2D[2];            /**< Maximum 2D texture dimensions */
    int          maxTexture2DMipmap[2];      /**< Maximum 2D mipmapped texture dimensions */
    int          maxTexture2DLinear[3];      /**< Maximum dimensions (width, height, pitch) for 2D textures bound to pitched memory */
    int          maxTexture2DGather[2];      /**< Maximum 2D texture dimensions if texture gather operations have to be performed */
    int          maxTexture3D[3];            /**< Maximum 3D texture dimensions */
    int          maxTexture3DAlt[3];         /**< Maximum alternate 3D texture dimensions */
    int          maxTextureCubemap;          /**< Maximum Cubemap texture dimensions */
    int          maxTexture1DLayered[2];     /**< Maximum 1D layered texture dimensions */
    int          maxTexture2DLayered[3];     /**< Maximum 2D layered texture dimensions */
    int          maxTextureCubemapLayered[2];/**< Maximum Cubemap layered texture dimensions */
    int          maxSurface1D;               /**< Maximum 1D surface size */
    int          maxSurface2D[2];            /**< Maximum 2D surface dimensions */
    int          maxSurface3D[3];            /**< Maximum 3D surface dimensions */
    int          maxSurface1DLayered[2];     /**< Maximum 1D layered surface dimensions */
    int          maxSurface2DLayered[3];     /**< Maximum 2D layered surface dimensions */
    int          maxSurfaceCubemap;          /**< Maximum Cubemap surface dimensions */
    int          maxSurfaceCubemapLayered[2];/**< Maximum Cubemap layered surface dimensions */
    size_t       surfaceAlignment;           /**< Alignment requirements for surfaces */
    int          concurrentKernels;          /**< Device can possibly execute multiple kernels concurrently */
    int          ECCEnabled;                 /**< Device has ECC support enabled */
    int          pciBusID;                   /**< PCI bus ID of the device */
    int          pciDeviceID;                /**< PCI device ID of the device */
    int          pciDomainID;                /**< PCI domain ID of the device */
    int          tccDriver;                  /**< 1 if device is a Tesla device using TCC driver, 0 otherwise */
    int          asyncEngineCount;           /**< Number of asynchronous engines */
    int          unifiedAddressing;          /**< Device shares a unified address space with the host */
    int          memoryClockRate;            /**< Peak memory clock frequency in kilohertz */
    int          memoryBusWidth;             /**< Global memory bus width in bits */
    int          l2CacheSize;                /**< Size of L2 cache in bytes */
    int          persistingL2CacheMaxSize;   /**< Device's maximum l2 persisting lines capacity setting in bytes */
    int          maxThreadsPerMultiProcessor;/**< Maximum resident threads per multiprocessor */
    int          streamPrioritiesSupported;  /**< Device supports stream priorities */
    int          globalL1CacheSupported;     /**< Device supports caching globals in L1 */
    int          localL1CacheSupported;      /**< Device supports caching locals in L1 */
    size_t       sharedMemPerMultiprocessor; /**< Shared memory available per multiprocessor in bytes */
    int          regsPerMultiprocessor;      /**< 32-bit registers available per multiprocessor */
    int          managedMemory;              /**< Device supports allocating managed memory on this system */
    int          isMultiGpuBoard;            /**< Device is on a multi-GPU board */
    int          multiGpuBoardGroupID;       /**< Unique identifier for a group of devices on the same multi-GPU board */
    int          hostNativeAtomicSupported;  /**< Link between the device and the host supports native atomic operations */
    int          singleToDoublePrecisionPerfRatio; /**< Ratio of single precision performance (in floating-point operations per second) to double precision performance */
    int          pageableMemoryAccess;       /**< Device supports coherently accessing pageable memory without calling cudaHostRegister on it */
    int          concurrentManagedAccess;    /**< Device can coherently access managed memory concurrently with the CPU */
    int          computePreemptionSupported; /**< Device supports Compute Preemption */
    int          canUseHostPointerForRegisteredMem; /**< Device can access host registered memory at the same virtual address as the CPU */
    int          cooperativeLaunch;          /**< Device supports launching cooperative kernels via ::cudaLaunchCooperativeKernel */
    int          cooperativeMultiDeviceLaunch; /**< Deprecated, cudaLaunchCooperativeKernelMultiDevice is deprecated. */
    size_t       sharedMemPerBlockOptin;     /**< Per device maximum shared memory per block usable by special opt in */
    int          pageableMemoryAccessUsesHostPageTables; /**< Device accesses pageable memory via the host's page tables */
    int          directManagedMemAccessFromHost; /**< Host can directly access managed memory on the device without migration. */
    int          maxBlocksPerMultiProcessor; /**< Maximum number of resident blocks per multiprocessor */
    int          accessPolicyMaxWindowSize;  /**< The maximum value of ::cudaAccessPolicyWindow::num_bytes. */
    size_t       reservedSharedMemPerBlock;  /**< Shared memory reserved by CUDA driver per block in bytes */
};
#include <stdio.h>
#include <cuda_runtime.h>

// 错误处理宏(自定义)
#define HANDLE_ERROR(err) (HandleError(err, __FILE__, __LINE__))
static void HandleError(cudaError_t err, const char* file, int line) {
    if (err != cudaSuccess) {
        printf("CUDA Error: %s in %s at line %d\n", cudaGetErrorString(err), file, line);
        exit(EXIT_FAILURE);
    }
}

int main() {
    int deviceCount;
    HANDLE_ERROR(cudaGetDeviceCount(&deviceCount));  // 获取设备数量

    if (deviceCount == 0) {
        printf("No CUDA devices found.\n");
        return 0;
    }

    for (int i = 0; i < deviceCount; i++) {
        cudaDeviceProp prop;
        HANDLE_ERROR(cudaGetDeviceProperties(&prop, i));  // 获取设备属性

        // 打印通用信息
        printf("\n===== General Information for Device %d =====\n", i);
        printf("  Name:                  %s\n", prop.name);
        printf("  Compute Capability:    %d.%d\n", prop.major, prop.minor);
        printf("  Clock Rate (MHz):      %d\n", prop.clockRate / 1000);
        printf("  Device Overlap:        %s\n", prop.deviceOverlap ? "Enabled" : "Disabled");
        printf("  Kernel Timeout:        %s\n", prop.kernelExecTimeoutEnabled ? "Enabled" : "Disabled");

        // 打印内存信息
        printf("\n  ----- Memory Information -----\n");
        printf("  Total Global Memory:   %.2f GB\n", (float)prop.totalGlobalMem / (1 << 30));
        printf("  Total Constant Memory: %.2f KB\n", (float)prop.totalConstMem / 1024);
        printf("  Memory Pitch:          %zu bytes\n", prop.memPitch);
        printf("  Texture Alignment:     %zu bytes\n", prop.textureAlignment);

        // 打印多处理器信息
        printf("\n  ----- Multiprocessor Details -----\n");
        printf("  Multiprocessor Count:  %d\n", prop.multiProcessorCount);
        printf("  Shared Mem Per Block:  %zu bytes\n", prop.sharedMemPerBlock);
        printf("  Registers Per Block:   %d\n", prop.regsPerBlock);
        printf("  Max Threads Per Block: %d\n", prop.maxThreadsPerBlock);
        printf("  Max Grid Size:         (%d, %d, %d)\n", 
               prop.maxGridSize[0], prop.maxGridSize[1], prop.maxGridSize[2]);
        printf("  Warp Size:             %d\n", prop.warpSize);
    }

    return 0;
}
int main(void) {
    cudaDeviceProp prop;         // 修正类型声明
    int dev;
    
    // 获取当前设备ID
    HANDLE_ERROR(cudaGetDevice(&dev));                          // 修正API名称和参数传递
    printf("ID of current CUDA device: %d\n", dev);             // 修正格式字符串
    
    // 查找计算能力1.3设备
    memset(&prop, 0, sizeof(cudaDeviceProp));                   // 修正memset参数
    prop.major = 1;                                             // 修正赋值符号
    prop.minor = 3;
    HANDLE_ERROR(cudaChooseDevice(&dev, &prop));                // 修正API名称
    printf("ID of CUDA device closest to compute capability 1.3: %d\n", dev);
    
    // 设置目标设备
    HANDLE_ERROR(cudaSetDevice(dev));
    return 0;
}

全局内存

使用 cudaMalloc 分配,并使用参数传递的内存空间。

cuda 并行编程

多个线程组成一维的线程块 block,多个线程块组成线程格 grid,线程格最高3维。

cpu实现向量相加:

#include <stdio.h>
#define N 10

// 数组加法函数(单线程CPU版本)
void add(int *a, int *b, *c) {
    int tid = 0;             // 线程索引从0开始
    while (tid < N) {
        c[tid] = a[tid] + b[tid];  // 修正符号错误:c'étid -> c[tid]
        tid += 1;            // 单线程每次递增1
    }
}

int main(void) {
    int a[N], b[N], c[N];
    
    // 初始化数组
    for (int i = 0; i < N; i++) {
        a[i] = -i;          // 数组a初始化为 [0, -1, -2,...-9]
        b[i] = i * i;       // 数组b初始化为 [0, 1, 4,...81]
    }

    add(a, b, c);           // 执行数组加法

    // 打印结果
    for (int i = 0; i < N; i++) {
        printf("%d + %d = %d\n", a[i], b[i], c[i]); // 修正格式符:&d -> %d
    }

    return 0;
}

可以通过多线程并行增加执行效率。而GPU主要用于多线程

下述代码,在主机中为向量赋值,复制到设备中进行求和,结果复制到主机中。调用核函数时尖括号中指定线程块数量,在核函数内部通过内置变量 blockIdx.x 获取当前线程块编号。blockIdx 是三维的,在图像等二维场景下更方便。线程块集合称为线程格Grid)。

#include <stdio.h>
#include <cuda_runtime.h>
#define N 10

// 错误检查宏(需配合书中的common/book.h)
#define HANDLE_ERROR(err) (HandleError(err, __FILE__, __LINE__))

// GPU核函数(第二张图修正)
__global__ void add(int *a, int *b, int *c) {
    int tid = blockIdx.x;    // 使用块索引定位数据位置
    if (tid < N) {
        c[tid] = a[tid] + b[tid];  // 修正c'étida -> c[tid]
    }
}

int main(void) {
    int a[N], b[N], c[N];
    int *dev_a, *dev_b, *dev_c;

    // GPU内存分配(第一张图修正)
    HANDLE_ERROR(cudaMalloc((void**)&dev_a, N * sizeof(int)));
    HANDLE_ERROR(cudaMalloc((void**)&dev_b, N * sizeof(int)));
    HANDLE_ERROR(cudaMalloc((void**)&dev_c, N * sizeof(int)));

    // 初始化主机数组
    for (int i = 0; i < N; i++) {
        a[i] = -i;          // 原图错误赋值符 == 修正为 =
        b[i] = i * i;       // 原图符号错误 」i -> i*i
    }

    // 数据拷贝到GPU(修正参数顺序)
    HANDLE_ERROR(cudaMemcpy(dev_a, a, N*sizeof(int), cudaMemcpyHostToDevice));
    HANDLE_ERROR(cudaMemcpy(dev_b, b, N*sizeof(int), cudaMemcpyHostToDevice));

    // 启动核函数(修正执行配置)
    add<<<N, 1>>>(dev_a, dev_b, dev_c);  // 使用N个块,每个块1个线程

    // 结果拷贝回CPU(修正参数)
    HANDLE_ERROR(cudaMemcpy(c, dev_c, N*sizeof(int), cudaMemcpyDeviceToHost));

    // 输出结果(修正格式符)
    for (int i = 0; i < N; i++) {
        printf("%d + %d = %d\n", a[i], b[i], c[i]);
    }

    // 释放GPU内存
    cudaFree(dev_a);
    cudaFree(dev_b);
    cudaFree(dev_c);

    return 0;
}

上述核函数在GPU线程执行中:

 不同版本GPU对线程格的维度有不同限制:

计算能力版本 x维度最大值 y维度最大值 z维度最大值
< 2.0 65535 65535 1
2.0~6.1 65535 65535 65535
≥7.0 2^31-1 65535 65535

 动态查询:

cudaDeviceProp prop;
cudaGetDeviceProperties(&prop, 0);
printf("Max grid dimensions: [%d, %d, %d]\n", 
       prop.maxGridSize[0], 
       prop.maxGridSize[1], 
       prop.maxGridSize[2]);

// 根据硬件自动调整grid维度
dim3 gridDim;
gridDim.x = min(desired_x, prop.maxGridSize[0]);
gridDim.y = min(desired_y, prop.maxGridSize[1]);
gridDim.z = min(desired_z, prop.maxGridSize[2]);
kernel<<<gridDim, blockDim>>>(...);

julia集计算

满足某个复数运行要求的点组成的集合

CPU

#include <stdio.h>
#include <stdlib.h>
#define DIM 1024    // 图像分辨率

// 复数结构体定义
// 复数结构体(CPU版本)
struct cuComplex {
    float r;  // 实部
    float i;  // 虚部

    // 构造函数
    cuComplex(float a, float b) : r(a), i(b) {}

    // 计算模的平方(避免开方运算)
    float magnitude2(void) {
        return r * r + i * i;
    }

    // 复数加法运算符重载
    cuComplex operator+(const cuComplex& a) {
        return cuComplex(r + a.r, i + a.i);
    }

    // 复数乘法运算符重载((a+bi)(c+di)=ac-bd + (ad+bc)i)
    cuComplex operator*(const cuComplex& a) {
        return cuComplex(r*a.r - i*a.i, r*a.i + i*a.r);
    }
};

// Julia集判断函数
// 判断点(x,y)是否属于Julia集
int julia(int x, int y) {
    const float scale = 1.5;    // 坐标系缩放因子
    // 将像素坐标映射到复平面 [-scale, scale]
    float jx = scale * (float)(DIM/2 - x)/(DIM/2);  // 计算复平面x坐标
    float jy = scale * (float)(DIM/2 - y)/(DIM/2);  // 计算复平面y坐标
    
    cuComplex c(-0.8, 0.156);   // Julia集常数c(决定形状)
    cuComplex a(jx, jy);        // 初始复数值z0 = (jx, jy)
    
    // 迭代计算:z_{n+1} = z_n^2 + c
    for(int i = 0; i < 200; i++) {
        a = a * a + c;          // 复数平方运算
        if(a.magnitude2() > 1000) // 判断是否发散
            return 0;           // 发散点不属于Julia集
    }
    return 1;                   // 收敛点属于Julia集
}

int main(void) {
    unsigned char *bitmap = (unsigned char*)malloc(DIM*DIM*4); // RGBA像素缓冲区
    
    // 遍历所有像素点(CPU单线程循环)
    for(int y = 0; y < DIM; y++) {
        for(int x = 0; x < DIM; x++) {
            // 计算当前点是否属于Julia集
            int juliaValue = julia(x, y);
            
            // 计算缓冲区偏移量(每个像素4字节:RGBA)
            int offset = (x + y * DIM) * 4;
            
            // 设置颜色(红色表示属于Julia集)
            bitmap[offset + 0] = 255 * juliaValue; // R
            bitmap[offset + 1] = 0;                // G
            bitmap[offset + 2] = 0;                // B
            bitmap[offset + 3] = 255;              // A
        }
    }
    
    // 此处应添加图像显示或保存代码(例如使用OpenGL/SDL库)
    // bitmap.display_and_exit();
    
    free(bitmap);
    return 0;
}

 GPU 版

#include "../common/book.h"        // CUDA错误处理头文件
#include "../common/cpu_bitmap.h"  // 位图操作头文件
#define DIM 1000                   // 图像分辨率

// ================= 复数结构体定义(设备端) =================
struct cuComplex {
    float r;  // 实部
    float i;  // 虚部

    // 构造函数(设备端)
    __device__ cuComplex(float a, float b) : r(a), i(b) {}

    // 计算复数模平方(设备端)
    __device__ float magnitude2(void) {
        return r * r + i * i;
    }

    // 复数加法重载(设备端)
    __device__ cuComplex operator+(const cuComplex& a) {
        return cuComplex(r + a.r, i + a.i);
    }

    // 复数乘法重载(设备端)公式:(a+bi)(c+di) = (ac-bd) + (ad+bc)i
    __device__ cuComplex operator*(const cuComplex& a) {
        return cuComplex(r*a.r - i*a.i, r*a.i + i*a.r);
    }
};

// ================= Julia集判定函数(设备端) =================
__device__ int julia(int x, int y) {
    const float scale = 1.5;  
    // 坐标映射到复平面[-scale, scale]
    float jx = scale * (float)(DIM/2 - x)/(DIM/2);  
    float jy = scale * (float)(DIM/2 - y)/(DIM/2);  

    cuComplex c(-0.8, 0.156);  // Julia集常数
    cuComplex a(jx, jy);       // 初始点z0

    // 迭代计算 z = z^2 + c
    for(int i=0; i<200; i++){  
        a = a * a + c;  
        if(a.magnitude2() > 1000)  // 发散判断
            return 0;  
    }
    return 1;  // 收敛点属于集合
}

// ================= GPU核函数(全局函数) =================
__global__ void kernel(unsigned char *ptr) {
    // 通过blockIdx获取二维像素坐标
    int x = blockIdx.x;        // 列索引(0~DIM-1)
    int y = blockIdx.y;        // 行索引(0~DIM-1)
    int offset = x + y * gridDim.x;  // 计算线性偏移(gridDim.x=DIM)

    // 计算Julia集值并设置颜色(RGBA)
    int juliaValue = julia(x, y);
    ptr[offset*4 + 0] = 255 * juliaValue;  // 红色分量
    ptr[offset*4 + 1] = 0;                 // 绿色分量
    ptr[offset*4 + 2] = 0;                 // 蓝色分量
    ptr[offset*4 + 3] = 255;               // Alpha通道
}

// ================= 主函数 =================
int main(void) {
    // 1. 创建CPU位图对象(用于显示)
    CPUBitmap bitmap(DIM, DIM); 
    unsigned char *dev_bitmap;

    // 2. GPU内存分配(DIM*DIM*4字节)
    HANDLE_ERROR(cudaMalloc((void**)&dev_bitmap, DIM*DIM*4));

    // 3. 配置执行参数(二维线程格,第三维置1)
    dim3 grid(DIM, DIM);  // 创建DIM x DIM x 1的线程格

    // 4. 启动核函数(每个block处理一个像素)
    kernel<<<grid, 1>>>(dev_bitmap);  // 每个block 1个线程

    // 5. 拷贝GPU结果到CPU位图
    HANDLE_ERROR(cudaMemcpy(bitmap.get_ptr(), dev_bitmap, 
                           DIM*DIM*4, cudaMemcpyDeviceToHost));

    // 6. 显示图像并释放资源
    bitmap.display_and_exit();
    HANDLE_ERROR(cudaFree(dev_bitmap));

    return 0;
}

GPU vs CPU实现对比

​特性​ ​CPU版本​ ​GPU版本​
​并行方式​ 单线程双层循环 二维线程格并行
​内存模型​ 直接访问主机内存 显存分配 + 主机-设备数据传输
​索引计算​ x + y*DIM blockIdx.x/y + gridDim.x
​函数修饰符​ __device__/__global__
​性能影响​ 受CPU单核性能限制 依赖GPU流处理器数量

线程协作

add<<<N, 1>>>(dev_a, dev_b, dev_c) 中N为线程格的规格,1为每个线程块中线程的数量。N * 1 即为并发线程数

线程块并发

// 核函数:每个线程块处理1个元素(1线程/块)
__global__ void add_block(int *a, int *b, int *c) {
    int tid = blockIdx.x;    // 直接使用块索引
    if (tid < N) {           // 边界检查
        c[tid] = a[tid] + b[tid];
    }
}

// 调用方式(启动N个线程块,每个块1线程)
add_block<<<N, 1>>>(dev_a, dev_b, dev_c);

线程并发

// CUDA核函数定义(全局函数)
__global__ void add(int *a, int *b, int *c) {
    // 获取线程ID(修正threadIdx为blockIdx以支持多块并行)
    int tid = threadIdx.x;       // 使用块索引代替线程索引
    
    if (tid < N) {              // 边界判断
        c[tid] = a[tid] + b[tid];  // 修正字符错误:'étid → [tid]
    }
}

add<<<1, N>>>(dev_a, dev_b, dev_c);  
​特征​ 线程块级并发 (add_block) 线程级并发 (add_thread)
​执行配置​ <<<N, 1>>> <<<(N+255)/256, 256>>>
​线程利用率​ 每个SM仅1线程运行 每个SM可调度数千线程
​最大支持数据量​ 受限于 maxGridSize(约65k) 理论支持 
​硬件资源占用​ 浪费SM计算资源 充分占用SM内的warp调度器
​适用场景​ 小规模数据(N < 1024) 大规模数据(N > 1e6)
​指令效率​ 低(线程间无warp合并) 高(warp内指令同步执行)
​显存访问模式​ 随机访问(相邻块可能访问不连续地址) 合并访问(相邻线程访问连续地址)
​典型性能​ 1x(基准) 可达10x+加速

避免使用线程块级并发​​:

  • Volta/Turing架构后,每个SM最多支持32个线程块
  • 若使用<<<N,1>>>,当N>32时产生排队延迟

 由于硬件会对线程数和线程块最大数进行限制,所以处理大规模数据时使用线程与线程块的结合。

// 核函数:每个线程处理1个元素(多线程/块)
__global__ void add_thread(int *a, int *b, int *c) {
    int tid = blockIdx.x * blockDim.x + threadIdx.x; // 计算全局索引
    while (tid < N) {           // <== 边界检查
        c[tid] = a[tid] + b[tid];
        tid += blockDim.x * gridDim.x;
    }
}

// 调用方式(动态计算网格维度)
int threadsPerBlock = 256;       // 推荐256/1024等2的幂
int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;  // <== 保证向上取整
add_thread<<<blocksPerGrid, threadsPerBlock>>>(dev_a, dev_b, dev_c);

 

参考二维数组嵌套二维数组。尖括号内内容可以是数字,也可以是 dim3 结构体定义的多维数组。

共享内存

使用 __share__ 修饰的变量放入共享内存,同一线程块中的每个线程都可以访问,实现并发场景下数据共享,存在竞态条件。

// 同步操作
void __syncthreads(); // 同步线程
int all_non_zero = __syncthreads_and(predicate); // 所有线程传入参数的逻辑与的结果
int any_non_zero = __syncthreads_or(predicate);  // 所有线程传入参数的逻辑或的结果
int active_threads = __syncthreads_count(predicate); // 所有线程传入参数的非零的个数

向量点乘操作的GPU实现

#include "../common/book.h"  // 引入CUDA错误处理宏HANDLE_ERROR

// 定义最小值宏(用于计算块数)
#define imin(a,b) (a < b ? a : b)

// 数据总长度:33 * 1024=33792
const int N = 33 * 1024;  

__global__ void dot(float *a, float *b, float *c) {
    // ===== 共享内存声明 =====
    __shared__ float cache[256];  // 每个块256线程共享缓存(需2的幂)
    
    // ===== 索引计算 =====
    int tid = threadIdx.x + blockIdx.x * blockDim.x;  // 全局线程索引
    int cacheIndex = threadIdx.x;                      // 共享内存索引
    
    // ===== 阶段1:跨网格累加点积 =====
    float tmp = 0.0f;
    while (tid < N) {              // 处理超出网格大小的数据
        tmp += a[tid] * b[tid];    // 计算部分点积
        tid += blockDim.x * gridDim.x; // 跳跃步长=总线程数
    }
    cache[cacheIndex] = tmp;       // 存储到共享内存
    
    // ===== 阶段2:树状归约 =====
    __syncthreads();  // 块内同步(所有线程必须到达此处)
    
    // 归约操作(256→128→64→...→1)
    for (int i = blockDim.x/2; i > 0; i /= 2) {
        if (cacheIndex < i) {
            cache[cacheIndex] += cache[cacheIndex + i];  // 累加相邻元素
        }
        __syncthreads();  // 每步归约后同步
    }
    
    // ===== 阶段3:写入全局内存 =====
    if (cacheIndex == 0) {
        c[blockIdx.x] = cache[0];  // 每个块输出一个结果
    }
}

int main(void) {
    // ===== 主机内存分配 =====
    float *a = new float[N];          // 输入向量a
    float *b = new float[N];          // 输入向量b
    float *partial_c = new float[32]; // 部分结果缓冲区(最多32块)

    // ===== 设备内存分配 =====
    float *dev_a, *dev_b, *dev_partial_c;
    HANDLE_ERROR(cudaMalloc((void**)&dev_a, N*sizeof(float)));
    HANDLE_ERROR(cudaMalloc((void**)&dev_b, N*sizeof(float)));
    HANDLE_ERROR(cudaMalloc((void**)&dev_partial_c, 32*sizeof(float)));

    // ===== 初始化输入数据 =====
    for(int i = 0; i < N; i++) {
        a[i] = i;        // a = [0, 1, 2,...,33791]
        b[i] = i * 2;    // b = [0, 2, 4,...,67582]
    }

    // ===== 数据传输到GPU =====
    HANDLE_ERROR(cudaMemcpy(dev_a, a, N*sizeof(float), cudaMemcpyHostToDevice));
    HANDLE_ERROR(cudaMemcpy(dev_b, b, N*sizeof(float), cudaMemcpyHostToDevice));

    // ===== 执行核函数 =====
    const int threadsPerBlock = 256;
    const int blocksPerGrid = imin(32, (N + threadsPerBlock - 1)/threadsPerBlock);
    dot<<<blocksPerGrid, threadsPerBlock>>>(dev_a, dev_b, dev_partial_c);

    // ===== 取回结果 =====
    HANDLE_ERROR(cudaMemcpy(partial_c, dev_partial_c, 
                           blocksPerGrid*sizeof(float), cudaMemcpyDeviceToHost));

    // ===== CPU端二次归约 =====
    float c = 0;
    for(int i = 0; i < blocksPerGrid; i++) {
        c += partial_c[i];  // 汇总所有块的结果
    }

    // ===== 验证公式 =====
    #define sum_of_squares(x) (x*(x+1)*(2*x+1)/6)  // 平方和公式
    printf("GPU结果: %.2f\n", c);
    printf("理论值: %.2f\n", 2 * sum_of_squares((float)(N-1)));

    // ===== 资源释放 =====
    cudaFree(dev_a);
    cudaFree(dev_b);
    cudaFree(dev_partial_c);
    delete[] a;
    delete[] b;
    delete[] partial_c;

    return 0;
}

错误优化:

if (cacheIndex < i) {
    cache[cacheIndex] += cache[cacheIndex + i];
    __syncthreads(); // 将同步操作放入条件语句,是的某些线程无需等待同步操作(×)
}

多线程中条件语句使得部分线程执行某些语句其他不执行,这叫线程发散,这种情况导致某些线程繁忙有些空闲。CUDA架构确保一个线程块中只有所有线程都执行__syncthreads();后线程才会继续执行,如果存在上述情况会导致无限等待。

常量内存

性能瓶颈通常不在gpu的计算单元,而是内存带宽。

光线追踪

想象一只眼睛从某个平面出发,所有从当前平面能“看见”的物体的颜色。

#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdlib.h>
#include <math.h>

#define DIM 1024               // 定义图像尺寸
#define SPHERES 20             // 定义球体数量
#define INF 1e6                // 定义无穷大值
#define rnd(x) (x * rand() / RAND_MAX)  // 随机数生成宏

// 球体数据结构(包含CPU和GPU版本)
struct Sphere {
    float r, g, b;           // 颜色值
    float radius;            // 半径
    float x, y, z;           // 中心坐标
    
    // 光线碰撞检测方法(设备端)
    __device__ float hit(float ox, float oy, float *n) {
        float dx = ox - x;   // x坐标差
        float dy = oy - y;   // y坐标差
        // 判断光线是否在球体投影圆内
        if (dx*dx + dy*dy < radius*radius) {
            float dz = sqrtf(radius*radius - dx*dx - dy*dy); // 计算z方向深度
            *n = dz / radius; // 存储法线方向分量
            return z + dz;    // 返回碰撞点z坐标(相机坐标系)
        }
        return -INF;         // 未碰撞返回负无穷
    }
};

// CUDA核函数:光线追踪计算
__global__ void kernel(Sphere *s, unsigned char *ptr) {
    // 计算像素坐标
    int x = threadIdx.x + blockIdx.x * blockDim.x;
    int y = threadIdx.y + blockIdx.y * blockDim.y;
    int offset = x + y * blockDim.x * gridDim.x;
    
    // 将坐标系原点移到图像中心
    float ox = (x - DIM/2);
    float oy = (y - DIM/2);
    
    // 初始化颜色和最近碰撞距离
    float r = 0, g = 0, b = 0;
    float max_dist = -INF;
    
    // 遍历所有球体
    for(int i=0; i<SPHERES; i++) {
        float n;
        float t = s[i].hit(ox, oy, &n); // 计算碰撞
        if(t > max_dist) {   // 发现更近的碰撞
            max_dist = t;
            // 使用法线分量计算颜色(简单着色)
            r = s[i].r * n;
            g = s[i].g * n;
            b = s[i].b * n;
        }
    }
    
    // 将计算结果写入显存(RGBA格式)
    ptr[offset*4 + 0] = (int)(r * 255); // 红色通道
    ptr[offset*4 + 1] = (int)(g * 255); // 绿色通道
    ptr[offset*4 + 2] = (int)(b * 255); // 蓝色通道
    ptr[offset*4 + 3] = 255;            // Alpha通道
}

int main(void) {
    // 初始化CUDA事件(用于计时)
    cudaEvent_t start, stop;
    cudaEventCreate(&start);
    cudaEventCreate(&stop);
    
    // 分配主机内存
    unsigned char *bitmap = new unsigned char[DIM*DIM*4];
    Sphere *temp_s = (Sphere*)malloc(sizeof(Sphere)*SPHERES);
    
    // 随机生成球体参数
    for(int i=0; i<SPHERES; i++) {
        temp_s[i].r = rnd(1.0f);        // 红色分量
        temp_s[i].g = rnd(1.0f);        // 绿色分量
        temp_s[i].b = rnd(1.0f);        // 蓝色分量
        temp_s[i].x = rnd(2000.0f) - 500;  // x坐标(-500~1500)
        temp_s[i].y = rnd(2000.0f) - 1000; // y坐标(-1000~1000)
        temp_s[i].z = rnd(2000.0f) - 500;  // z坐标(-500~1500)
        temp_s[i].radius = rnd(100.0f) + 20; // 半径(20~120)
    }
    
    // 分配设备内存
    Sphere *dev_s;
    unsigned char *dev_bitmap;
    cudaMalloc(&dev_s, sizeof(Sphere)*SPHERES);
    cudaMalloc(&dev_bitmap, DIM*DIM*4);
    
    // 拷贝数据到设备
    cudaMemcpy(dev_s, temp_s, sizeof(Sphere)*SPHERES, cudaMemcpyHostToDevice);
    
    // 设置CUDA核执行配置
    dim3 grids(DIM/16, DIM/16);  // 网格配置(64x64)
    dim3 threads(16, 16);        // 线程块配置(16x16)
    
    // 启动核函数
    cudaEventRecord(start);
    kernel<<<grids, threads>>>(dev_s, dev_bitmap);
    cudaEventRecord(stop);
    
    // 回传计算结果
    cudaMemcpy(bitmap, dev_bitmap, DIM*DIM*4, cudaMemcpyDeviceToHost);
    
    // 清理资源
    free(temp_s);
    cudaFree(dev_s);
    cudaFree(dev_bitmap);
    cudaEventDestroy(start);
    cudaEventDestroy(stop);
    
    // 这里可以添加图像显示代码(需配合图形库)
    return 0;
}

常量内存优化:__constant__ 和 cudaMemcpyToSymbol

// 核心修改部分(原代码基础上添加常量内存支持)
__constant__ Sphere dev_spheres[SPHERES]; // 常量内存声明

__global__ void kernel(unsigned char* ptr) {
    // ...(原有像素计算逻辑不变)...
    // 修改后的碰撞检测逻辑(从常量内存读取)
    for(int i=0; i<SPHERES; i++){
        float n;
        float t = dev_spheres[i].hit(ox, oy, &n); // 从常量内存访问
        // ...(后续着色逻辑不变)...
    }
}

int main() {
    // ...(原有初始化逻辑)...
    
    // 修改后的内存操作(使用常量内存专用API)
    cudaMemcpyToSymbol(dev_spheres, temp_s, sizeof(Sphere)*SPHERES);
    
    // ...(原有核函数调用和清理逻辑)...
}
/*
__constant__ 修饰符:声明设备端常量内存数组
cudaMemcpyToSymbol:CUDA专用API,用于将主机数据复制到常量内存
核函数参数调整:移除Sphere指针参数(直接从常量内存访问)
内存访问优化:将高频读取的球体数据置于快速访问路径
*/

 特性与优势:​

  1. ​硬件级缓存​​:NVIDIA GPU为常量内存提供专门的片上缓存(通常64KB),支持广播机制,当所有线程访问相同地址时能实现超高带宽
  2. ​只读优化​​:专为只读数据设计,适合存储光照参数、材质属性等高频访问数据
  3. ​访问特性​​:
    • 单次访问可被多个线程复用(适合warp内线程访问相同数据)
    • 延迟低于全局内存(通过缓存机制实现)
    • __constant__变量的访问会被编译器特殊优化
  4. ​使用场景​​:非常适合光线追踪中的场景参数、物理引擎的约束条件等需要被所有线程频繁读取的公共数据

​开发注意事项:​

  1. 容量限制:当前架构最大支持64KB常量内存
  2. 声明方式:必须使用__constant__限定符
  3. 数据传输:必须通过cudaMemcpyToSymbol而非普通cudaMemcpy
  4. 访问模式:尽量让线程束内线程访问相同内存地址以获得最佳性能

事件

基于GPU内部时钟的相对时间标记,当调用cudaEventRecord(event, stream)时,会在指定流中插入一个标记点,GPU执行到此位置时记录此刻的硬件时钟值。毫秒级精度

cudaEvent_t start, stop;
cudaEventCreate(&start); 
cudaEventCreate(&stop);
cudaEventRecord(start, 0);  // 记录起始时间戳

kernel<<<...>>>();          // 执行内核

cudaEventRecord(stop, 0);   // 记录结束时间戳
cudaEventSynchronize(stop);  // 等待所有线程事件完成

float elapsedTime;
cudaEventElapsedTime(&elapsedTime, start, stop); // 计算时间差

cudaEventDestroy(start);
cudaEventDestroy(stop);

纹理内存

纹理内存是CUDA中一种特殊的只读内存,适用于具有空间局部性的数据访问模式(如图像处理、热传导模拟等)。其核心优势在于内置的缓存机制,能够高效处理不规则的内存访问,提升数据读取速度。绑定/解绑操作需在主机端调用。

  1. ​缓存优化​​:自动缓存数据,适合空间局部性访问。
  2. ​边界处理​​:支持自动处理越界访问(如钳制或环绕)。
  3. ​多种数据格式​​:支持浮点数、整数等,并可进行归一化处理。
// 声明纹理对象
texture<float, 2> texRef;  // 2D浮点纹理

// 绑定纹理内存,指定通道描述
cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc<float>();
cudaBindTexture2D(NULL, texRef, dev_data, channelDesc, width, height, pitch);
//dev_data: 设备端数据指针。
//width, height: 数据维度。
//pitch: 内存步长(字节)。

// 核函数中访问纹理
__global__ void kernel() {
    int x = blockIdx.x * blockDim.x + threadIdx.x;
    int y = blockIdx.y * blockDim.y + threadIdx.y;
    float value = tex2D(texRef, x, y);  // 读取纹理坐标(x,y)的值
}

// 解绑纹理内存
cudaUnbindTexture(texRef);

热传导示例:简单的二维网格中热量从温度高的传到温度低的。极大简化后的公式:

#include <cuda_runtime.h>
#include <iostream>

// 声明2D浮点纹理引用
texture<float, 2> texTemperature;

__global__ void updateTemperature(float* newTemp, int width, int height) {
    int x = blockIdx.x * blockDim.x + threadIdx.x;
    int y = blockIdx.y * blockDim.y + threadIdx.y;
    if (x >= width || y >= height) return;

    // 从纹理内存读取周围温度值
    float center = tex2D(texTemperature, x, y);
    float left = tex2D(texTemperature, x-1, y);
    float right = tex2D(texTemperature, x+1, y);
    float top = tex2D(texTemperature, x, y-1);
    float bottom = tex2D(texTemperature, x, y+1);

    // 计算新温度(简化公式)
    newTemp[y*width + x] = (center + left + right + top + bottom) * 0.2f;
}

int main() {
    const int width = 512, height = 512;
    float* dev_temp;
    cudaMalloc(&dev_temp, width*height*sizeof(float));

    // 初始化温度数据...
    
    // 绑定纹理
    cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc<float>();
    cudaBindTexture2D(NULL, texTemperature, dev_temp, channelDesc, width, height, width*sizeof(float));

    // 启动核函数
    dim3 blocks(16, 16);
    dim3 grids((width + blocks.x - 1)/blocks.x, (height + blocks.y - 1)/blocks.y);
    updateTemperature<<<grids, blocks>>>(dev_temp, width, height);

    // 解绑纹理
    cudaUnbindTexture(texTemperature);
    cudaFree(dev_temp);
    return 0;
}

原子性

atomicAdd, atomicSub, atomicExch, atomicMin, atomicMax, atomicInc, atomicDec, atomicCAS, atomicAnd, atomicOr, atomicXor

不同版本gpu支持不同指令集,nvcc -arch=sm_11 指定编译代码需要1.1版本或更高

计算频率的main函数

#include <cuda_runtime.h>
#include <stdio.h>

// 定义CUDA错误检查宏
#define HANDLE_ERROR(err) (cudaErrorCheck(err, __FILE__, __LINE__))
inline void cudaErrorCheck(cudaError_t err, const char *file, int line) {
    if (err != cudaSuccess) {
        printf("%s in %s at line %d\n", cudaGetErrorString(err), file, line);
        exit(EXIT_FAILURE);
    }
}

// 直方图核函数声明
__global__ void histo_kernel(unsigned char* buffer, long size, unsigned int* histo);

int main(void) {
    // 主机内存分配
    unsigned char* buffer = (unsigned char*)big_random_block(SIZE); // 生成随机测试数据
    unsigned int histo[256] = {0};      // CPU端直方图结果
    
    // 设备内存指针
    unsigned char* dev_buffer = NULL;   // GPU输入数据
    unsigned int* dev_histo = NULL;     // GPU直方图结果

    // CUDA事件计时
    cudaEvent_t start, stop;
    HANDLE_ERROR(cudaEventCreate(&start)); // 创建开始事件
    HANDLE_ERROR(cudaEventCreate(&stop));  // 创建结束事件
    HANDLE_ERROR(cudaEventRecord(start, 0)); // 记录起始时间

    // GPU内存分配
    HANDLE_ERROR(cudaMalloc((void**)&dev_buffer, SIZE)); // 分配输入数据内存
    HANDLE_ERROR(cudaMalloc((void**)&dev_histo, 256 * sizeof(int))); // 分配直方图内存
    
    // 数据拷贝到设备
    HANDLE_ERROR(cudaMemcpy(dev_buffer, buffer, SIZE, cudaMemcpyHostToDevice));
    HANDLE_ERROR(cudaMemset(dev_histo, 0, 256 * sizeof(int))); // 设备内存清零

    // 获取GPU设备属性
    cudaDeviceProp prop;
    HANDLE_ERROR(cudaGetDeviceProperties(&prop, 0));
    int blocks = prop.multiProcessorCount; // 获取SM数量

    // 启动核函数
    dim3 grid(blocks * 2, 1);   // 根据SM数量计算网格维度
    dim3 threads(256, 1);       // 每个块256线程
    histo_kernel<<<grid, threads>>>(dev_buffer, SIZE, dev_histo);

    // 记录结束时间并同步
    HANDLE_ERROR(cudaEventRecord(stop, 0));
    HANDLE_ERROR(cudaEventSynchronize(stop));
    
    // 计算耗时
    float elapsedTime;
    HANDLE_ERROR(cudaEventElapsedTime(&elapsedTime, start, stop));
    printf("Time to generate: %3.1f ms\n", elapsedTime);

    // 拷贝结果回主机
    HANDLE_ERROR(cudaMemcpy(histo, dev_histo, 256 * sizeof(int), cudaMemcpyDeviceToHost));

    // 验证直方图总和
    long histoCount = 0;
    for (int i = 0; i < 256; i++) {
        histoCount += histo[i]; // 累加所有bin的计数
    }
    printf("Histogram Sum: %ld\n", histoCount);

    // 结果验证(与CPU计算对比)
    for (int i = 0; i < SIZE; i++) {
        histo[buffer[i]]--;     // 减去每个元素的计数
    }
    for (int i = 0; i < 256; i++) {
        if (histo[i] != 0) {
            printf("数据不一致,错误发生在bin %d\n", i);
        }
    }

    // 释放资源
    HANDLE_ERROR(cudaEventDestroy(start));
    HANDLE_ERROR(cudaEventDestroy(stop));
    HANDLE_ERROR(cudaFree(dev_histo));
    HANDLE_ERROR(cudaFree(dev_buffer));
    free(buffer);
    return 0;
}

使用全局内存原子操作的直方图核函数:atomicAdd
相比cpu上运行的版本更慢许多,可能是因为数据竞争导致操作被串行化

// 宏定义用于控制测试数据量
#define SIZE (100 * 1024 * 1024)  // 100MB大小的测试数据

// 直方图计算核函数
__global__ void histo_kernel(unsigned char *buffer, long size, unsigned int *histo) 
{
    // 线程全局索引计算
    int i = threadIdx.x + blockIdx.x * blockDim.x;
    
    // 步长计算(同步修改blockdim.griddim的大小写错误) 
    int stride = blockDim.x * gridDim.x;  // 总线程数 = 块数×每块线程数
    
    // 网格跨步循环模式(解决大数据量中的线程不足问题)
    while (i < size) 
    {
        // 原子操作统计直方图
        atomicAdd(&histo[buffer[i]], 1);  // 正确形式:缓冲区值作为索引
        
        // 以步长递增跳过已处理元素(确保所有数据被覆盖)
        i += stride;  // 固定写法
    }
}

使用共享内存原子操作和全局内存原子操作的直方图核函数

为了减少大量线程在少量地址上发生竞争,需要将直方图计算分为两个阶段

  1. 每个线程块中线程在共享内存中计算直方图,避免每次写入操作从芯片发送到DRAM。此时更少的线程在更少的地址上发生更少竞争
    1. 使用共享内存保存临时直方图,同步操作保证初始化操作在线程继续前完成
    2. 在局部直方图中统计,并同步操作
  2. 将临时直方图合并到全局直方图
__global__ void histo_kernel(unsigned char* buffer, long size, unsigned int* histo) 
{
    // 共享内存声明(每个block分配256个int)
    __shared__ unsigned int temp[256];
    
    // 1.1 线程局部初始化共享内存
    if(threadIdx.x < 256) {          // 仅前256个线程执行
        temp[threadIdx.x] = 0;       // 初始化当前block的共享内存直方图
    }
    __syncthreads();                 // 块内线程同步

    // 1.2 计算临时直方图
    // 计算全局索引和步长
    int i = blockIdx.x * blockDim.x + threadIdx.x; // 全局线程索引
    int offset = blockDim.x * gridDim.x;           // 总线程数(步长)
    
    // 网格跨步循环处理数据
    while(i < size) {
        atomicAdd(&temp[buffer[i]], 1); // 向共享内存原子累加
        i += offset;                    // 跨步处理后续数据
    }
    __syncthreads();                   // 确保块内所有计算完成

    // 1.3 计算全局直方图
    // 将共享内存结果合并到全局内存
    if(threadIdx.x < 256) {          // 仅前256个线程执行
        atomicAdd(&histo[threadIdx.x], temp[threadIdx.x]); // 全局原子操作
    }
}

不同线程执行完全不相干的任务。

页锁定内存

malloc 分配可分页的(Pagable)主机内存, cudaHostAlloc 分配主机上的页锁定的主机内存,页锁定内存也称固定内存(Pinned Memory)或不可分页内存。操作系统不会对这块内存分页并交换到磁盘,从而确保该内存始终驻留在物理内存中。应用可以访问该内存的物理地址,因为其不会被破坏或重新定位。

进而可以通过DMA进行GPU与host间的数据复制,建议仅对 cudaMemcpy 调用的内存使用页锁定内存。

#include <cuda_runtime.h>
#include <stdio.h>

// 错误处理宏
#define HANDLE_ERROR(err) (cudaErrorCheck(err, __FILE__, __LINE__))
static void cudaErrorCheck(cudaError_t err, const char *file, int line) {
    if (err != cudaSuccess) {
        printf("%s in %s at line %d\n", cudaGetErrorString(err), file, line);
        exit(EXIT_FAILURE);
    }
}

#define SIZE (10 * 1024 * 1024) // 测试数据量:10MB

// 测试普通设备内存性能
float cuda_malloc_test(int size, bool up) {
    cudaEvent_t start, stop;
    int *a, *dev_a;
    float elapsedTime;

    // 创建计时事件(修正参数括号错误)
    HANDLE_ERROR(cudaEventCreate(&start));
    HANDLE_ERROR(cudaEventCreate(&stop));

    // 分配主机和设备内存
    a = (int*)malloc(size * sizeof(int));       // 可分页主机内存
    HANDLE_ERROR(cudaMalloc(&dev_a, size * sizeof(int))); // 设备内存

    // 计时开始
    HANDLE_ERROR(cudaEventRecord(start, 0));
    
    // 执行100次内存复制(修正循环括号和参数顺序)
    for (int i = 0; i < 100; i++) {
        if (up) // 根据up标志决定复制方向
            HANDLE_ERROR(cudaMemcpy(dev_a, a, size*sizeof(int), cudaMemcpyHostToDevice));
        else
            HANDLE_ERROR(cudaMemcpy(a, dev_a, size*sizeof(int), cudaMemcpyDeviceToHost));
    }
    
    // 计时结束并计算
    HANDLE_ERROR(cudaEventRecord(stop, 0));
    HANDLE_ERROR(cudaEventSynchronize(stop));
    HANDLE_ERROR(cudaEventElapsedTime(&elapsedTime, start, stop));

    // 释放资源
    free(a);
    HANDLE_ERROR(cudaFree(dev_a));
    HANDLE_ERROR(cudaEventDestroy(start));
    HANDLE_ERROR(cudaEventDestroy(stop));

    return elapsedTime;
}

// 测试固定内存性能
float cuda_host_alloc_test(int size, bool up) {
    cudaEvent_t start, stop;
    int *a, *dev_a;
    float elapsedTime;

    HANDLE_ERROR(cudaEventCreate(&start));
    HANDLE_ERROR(cudaEventCreate(&stop));

    // 分配固定主机内存
    HANDLE_ERROR(cudaHostAlloc(&a, size*sizeof(int), cudaHostAllocDefault));
    HANDLE_ERROR(cudaMalloc(&dev_a, size*sizeof(int)));

    // 执行测试
    HANDLE_ERROR(cudaEventRecord(start, 0));
    for (int i = 0; i < 100; i++) {
        if (up)
            HANDLE_ERROR(cudaMemcpy(dev_a, a, size*sizeof(int), cudaMemcpyHostToDevice));
        else
            HANDLE_ERROR(cudaMemcpy(a, dev_a, size*sizeof(int), cudaMemcpyDeviceToHost));
    }
    HANDLE_ERROR(cudaEventRecord(stop, 0));
    HANDLE_ERROR(cudaEventSynchronize(stop));
    HANDLE_ERROR(cudaEventElapsedTime(&elapsedTime, start, stop));

    // 释放固定内存
    HANDLE_ERROR(cudaFreeHost(a));
    HANDLE_ERROR(cudaFree(dev_a));
    HANDLE_ERROR(cudaEventDestroy(start));
    HANDLE_ERROR(cudaEventDestroy(stop));

    return elapsedTime;
}

int main() {
    float elapsedTime;
    const float MB = (float)100*SIZE/1024/1024; // 总传输量:100次×10MB=1000MB

    // 测试普通内存上行性能(Host->Device)
    elapsedTime = cuda_malloc_test(SIZE, true);
    printf("cudaMalloc 上行时间: %3.1f ms\n", elapsedTime);
    printf("\t传输速率: %3.1f MB/s\n", MB*1000/elapsedTime);

    // 测试普通内存下行性能(Device->Host)
    elapsedTime = cuda_malloc_test(SIZE, false);
    printf("cudaMalloc 下行时间: %3.1f ms\n", elapsedTime);
    printf("\t传输速率: %3.1f MB/s\n", MB*1000/elapsedTime);

    // 测试固定内存上行性能
    elapsedTime = cuda_host_alloc_test(SIZE, true);
    printf("cudaHostAlloc 上行时间: %3.1f ms\n", elapsedTime);
    printf("\t传输速率: %3.1f MB/s\n", MB*1000/elapsedTime);

    // 测试固定内存下行性能
    elapsedTime = cuda_host_alloc_test(SIZE, false);
    printf("cudaHostAlloc 下行时间: %3.1f ms\n", elapsedTime);
    printf("\t传输速率: %3.1f MB/s\n", MB*1000/elapsedTime);

    return 0;
}

CUDA 流

事件概念中record函数的第二个参数就是用于指定插入事件的流 Stream

cudaEvent_t start, stop;
cudaEventCreate(&start); 
cudaEventRecord(start, 0);

CUDA流表示一个以指定顺序执行的GPU操作队列,可以添加核函数启动、内存复制、事件启动结束等操作,操作的添加顺序就是他们的执行顺序。可以将流视为一个GPU上的任务,且任务可以并行执行。

API:

cudaStreamCreate(&stream);  // 创建流对象
cudaStreamDestroy(stream);  // 释放流资源

cudaMemcpyAsync(..., stream); 

kernel<<<grid, block, sharedMem, stream>>>();

cudaStreamSynchronize(stream); // 等待流完成
cudaDeviceSynchronize();       // 等待所有流

cudaHostAlloc() // 分配固定内存

单个CUDA流

仅当使用多个流时才显现出流的威力

  1. 选择支持设备重叠功能的设备,这种GPU在执行一个cuda核函数同时还能在设备和主机间复制数据
  2. 主机上 cudaHostMalloc 分配页锁定内存,使用 cudaMemcpy 复制数据
  3. 在kernel函数执行前后,将数据分块在设备和主机间复制
    1. 使用cudaMemcpyAsync 复制,将操作放入stream流中
    2. 因为是异步执行,主机代码无法确定函数的执行进度
    3. 流中的代码按照加入流的顺序执行
    4. kernel 的尖括号中可增加一个流参数,这是核函数就是异步执行的
  4. 使用 cudaStreamSynchronize(stream) 等待异步执行的流完成
#include <cuda_runtime.h>
#include <stdio.h>
#include <stdlib.h>

// 错误检查宏
#define HANDLE_ERROR(err) (HandleError(err, __FILE__, __LINE__))
static void HandleError(cudaError_t err, const char* file, int line) {
    if (err != cudaSuccess) {
        printf("%s in %s at line %d\n", cudaGetErrorString(err), file, line);
        exit(EXIT_FAILURE);
    }
}

// 常量定义
#define N (1024 * 1024)         // 单个数据块大小
#define FULL_DATA_SIZE (N*20)  // 总数据量:20个块

// 核函数,内容不重要
__global__ void process_kernel(int *a, int *b, int *c) {
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    if (idx < N) {
        // 示例计算:相邻元素的均值滤波
        int idx1 = (idx + 1) % 256;
        int idx2 = (idx + 2) % 256;
        float as = (a[idx] + a[idx1] + a[idx2]) / 3.0f;
        float bs = (b[idx] + b[idx1] + b[idx2]) / 3.0f;
        c[idx] = (as + bs) / 2;
    }
}

int main() {
    // 设备检测
    cudaDeviceProp prop;
    int devID;
    HANDLE_ERROR(cudaGetDevice(&devID));
    HANDLE_ERROR(cudaGetDeviceProperties(&prop, devID));
    if (!prop.deviceOverlap) {
        printf("设备不支持流重叠,无法加速\n");
        return 0;
    }

    // 计时事件
    cudaEvent_t start, stop;
    HANDLE_ERROR(cudaEventCreate(&start));
    HANDLE_ERROR(cudaEventCreate(&stop));
    
    // 创建CUDA流(核心对象)
    cudaStream_t stream;
    HANDLE_ERROR(cudaStreamCreate(&stream));

    // 主机内存分配(固定内存)
    int *host_a, *host_b, *host_c;
    HANDLE_ERROR(cudaHostAlloc(&host_a, FULL_DATA_SIZE*sizeof(int), cudaHostAllocDefault));
    HANDLE_ERROR(cudaHostAlloc(&host_b, FULL_DATA_SIZE*sizeof(int), cudaHostAllocDefault));
    HANDLE_ERROR(cudaHostAlloc(&host_c, FULL_DATA_SIZE*sizeof(int), cudaHostAllocDefault));
    
    // 设备内存分配
    int *dev_a, *dev_b, *dev_c;
    HANDLE_ERROR(cudaMalloc(&dev_a, N*sizeof(int)));
    HANDLE_ERROR(cudaMalloc(&dev_b, N*sizeof(int)));
    HANDLE_ERROR(cudaMalloc(&dev_c, N*sizeof(int)));

    // 初始化数据
    for (int i=0; i<FULL_DATA_SIZE; i++) {
        host_a[i] = rand() % 256;
        host_b[i] = rand() % 256;
    }

    // 开始计时
    HANDLE_ERROR(cudaEventRecord(start, 0));

    // 分块处理(流式并行核心)
    for (int i=0; i<FULL_DATA_SIZE; i+=N) {
        // 异步内存复制(主机->设备)
        HANDLE_ERROR(cudaMemcpyAsync(dev_a, host_a+i, N*sizeof(int), 
                    cudaMemcpyHostToDevice, stream));
        HANDLE_ERROR(cudaMemcpyAsync(dev_b, host_b+i, N*sizeof(int), 
                    cudaMemcpyHostToDevice, stream));

        // 启动核函数(指定流)
        dim3 block(256);
        dim3 grid((N + block.x -1)/block.x);
        process_kernel<<<grid, block, 0, stream>>>(dev_a, dev_b, dev_c);

        // 异步内存复制(设备->主机)
        HANDLE_ERROR(cudaMemcpyAsync(host_c+i, dev_c, N*sizeof(int),
                    cudaMemcpyDeviceToHost, stream));
    }

    // 流同步等待所有操作完成
    HANDLE_ERROR(cudaStreamSynchronize(stream));
    
    // 停止计时
    HANDLE_ERROR(cudaEventRecord(stop, 0));
    HANDLE_ERROR(cudaEventSynchronize(stop));
    float elapsedTime;
    HANDLE_ERROR(cudaEventElapsedTime(&elapsedTime, start, stop));
    printf("总耗时: %.1f ms\n", elapsedTime);

    // 资源释放
    HANDLE_ERROR(cudaFreeHost(host_a));
    HANDLE_ERROR(cudaFreeHost(host_b));
    HANDLE_ERROR(cudaFreeHost(host_c));
    HANDLE_ERROR(cudaFree(dev_a));
    HANDLE_ERROR(cudaFree(dev_b));
    HANDLE_ERROR(cudaFree(dev_c));
    HANDLE_ERROR(cudaStreamDestroy(stream));
    HANDLE_ERROR(cudaEventDestroy(start));
    HANDLE_ERROR(cudaEventDestroy(stop));
    
    return 0;
}

多个CUDA流

实现流水线效果

#include <cuda_runtime.h>
#include <stdio.h>
#include <stdlib.h>

#define N (1024 * 1024)       // 每个数据块大小
#define FULL_DATA_SIZE (N*20) // 总数据量:20个块

// 错误检查宏
#define HANDLE_ERROR(err) (HandleError(err, __FILE__, __LINE__))
static void HandleError(cudaError_t err, const char* file, int line) {
    if (err != cudaSuccess) {
        printf("%s in %s at line %d\n", cudaGetErrorString(err), file, line);
        exit(EXIT_FAILURE);
    }
}

// 示例核函数(数据滤波处理)
__global__ void kernel(int* a, int* b, int* c) {
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    if (idx < N) {
        c[idx] = (a[idx] + b[idx]) / 2; // 简单均值计算
    }
}

int main() {
    // 设备能力检测
    cudaDeviceProp prop;
    int whichDevice;
    HANDLE_ERROR(cudaGetDevice(&whichDevice));
    HANDLE_ERROR(cudaGetDeviceProperties(&prop, whichDevice));
    if (!prop.deviceOverlap) {
        printf("设备不支持流重叠,无法加速\n");
        return 0;
    }

    // 计时事件
    cudaEvent_t start, stop;
    HANDLE_ERROR(cudaEventCreate(&start));
    HANDLE_ERROR(cudaEventCreate(&stop));

    // 创建两个流
    cudaStream_t stream0, stream1;
    HANDLE_ERROR(cudaStreamCreate(&stream0));
    HANDLE_ERROR(cudaStreamCreate(&stream1));

    // 分配页锁定主机内存(固定内存)
    int *host_a, *host_b, *host_c;
    HANDLE_ERROR(cudaHostAlloc(&host_a, FULL_DATA_SIZE*sizeof(int), cudaHostAllocDefault));
    HANDLE_ERROR(cudaHostAlloc(&host_b, FULL_DATA_SIZE*sizeof(int), cudaHostAllocDefault));
    HANDLE_ERROR(cudaHostAlloc(&host_c, FULL_DATA_SIZE*sizeof(int), cudaHostAllocDefault));

    // 初始化数据
    for(int i=0; i<FULL_DATA_SIZE; i++) {
        host_a[i] = rand() % 256;
        host_b[i] = rand() % 256;
    }

    // 分配设备内存(双流双缓冲)
    int *dev_a0, *dev_b0, *dev_c0; // 流0使用的设备内存
    int *dev_a1, *dev_b1, *dev_c1; // 流1使用的设备内存
    HANDLE_ERROR(cudaMalloc(&dev_a0, N*sizeof(int)));
    HANDLE_ERROR(cudaMalloc(&dev_b0, N*sizeof(int)));
    HANDLE_ERROR(cudaMalloc(&dev_c0, N*sizeof(int)));
    HANDLE_ERROR(cudaMalloc(&dev_a1, N*sizeof(int)));
    HANDLE_ERROR(cudaMalloc(&dev_b1, N*sizeof(int)));
    HANDLE_ERROR(cudaMalloc(&dev_c1, N*sizeof(int)));

    // 开始计时
    HANDLE_ERROR(cudaEventRecord(start, 0));

    // 流式并行处理主循环
    for(int i=0; i<FULL_DATA_SIZE; i+=N*2) {
        //==== 流0处理第i个数据块 ====//
        // 异步拷贝输入数据(Host->Device)
        HANDLE_ERROR(cudaMemcpyAsync(dev_a0, host_a+i, N*sizeof(int), 
                    cudaMemcpyHostToDevice, stream0));
        HANDLE_ERROR(cudaMemcpyAsync(dev_b0, host_b+i, N*sizeof(int), 
                    cudaMemcpyHostToDevice, stream0));
        
        // 启动核函数
        dim3 block(256);
        dim3 grid((N + block.x -1)/block.x);
        kernel<<<grid, block, 0, stream0>>>(dev_a0, dev_b0, dev_c0);
        
        // 异步拷贝输出数据(Device->Host)
        HANDLE_ERROR(cudaMemcpyAsync(host_c+i, dev_c0, N*sizeof(int),
                    cudaMemcpyDeviceToHost, stream0));

        //==== 流1处理第i+1个数据块 ====//
        // 异步拷贝输入数据(Host->Device)
        HANDLE_ERROR(cudaMemcpyAsync(dev_a1, host_a+i+N, N*sizeof(int), 
                    cudaMemcpyHostToDevice, stream1));
        HANDLE_ERROR(cudaMemcpyAsync(dev_b1, host_b+i+N, N*sizeof(int), 
                    cudaMemcpyHostToDevice, stream1));
        
        // 启动核函数
        kernel<<<grid, block, 0, stream1>>>(dev_a1, dev_b1, dev_c1);
        
        // 异步拷贝输出数据(Device->Host)
        HANDLE_ERROR(cudaMemcpyAsync(host_c+i+N, dev_c1, N*sizeof(int),
                    cudaMemcpyDeviceToHost, stream1));
    }

    // 同步所有流
    HANDLE_ERROR(cudaStreamSynchronize(stream0));
    HANDLE_ERROR(cudaStreamSynchronize(stream1));
    
    // 停止计时
    HANDLE_ERROR(cudaEventRecord(stop, 0));
    HANDLE_ERROR(cudaEventSynchronize(stop));
    float elapsedTime;
    HANDLE_ERROR(cudaEventElapsedTime(&elapsedTime, start, stop));
    printf("总耗时: %.1f ms\n", elapsedTime);

    // 释放资源
    HANDLE_ERROR(cudaFreeHost(host_a));
    HANDLE_ERROR(cudaFreeHost(host_b));
    HANDLE_ERROR(cudaFreeHost(host_c));
    HANDLE_ERROR(cudaFree(dev_a0));
    HANDLE_ERROR(cudaFree(dev_b0));
    HANDLE_ERROR(cudaFree(dev_c0));
    HANDLE_ERROR(cudaFree(dev_a1));
    HANDLE_ERROR(cudaFree(dev_b1));
    HANDLE_ERROR(cudaFree(dev_c1));
    HANDLE_ERROR(cudaStreamDestroy(stream0));
    HANDLE_ERROR(cudaStreamDestroy(stream1));
    HANDLE_ERROR(cudaEventDestroy(start));
    HANDLE_ERROR(cudaEventDestroy(stop));
    
    return 0;
}

经测试发现,并没有太快。要从GPU任务调度角度去看

GPU 工作调度原理

从用户的角度不同流是独立的,但CPU角度:一个或多个引擎执行复制操作,一个引擎执行核函数;这些引擎彼此独立操作队列中元素。

上一个多流任务的调度如下:

其中一号流的任务要在零号流的操作全部接收后才可以开始,依赖关系如下:

分析可知执行时间线如下:

优化后的执行时间线,通常应该使用宽度优先或轮询方式将工作分配到流:

for(int i=0; i<FULL_DATA_SIZE; i += N*2) {
    //==== 流0处理第i个数据块 ====//
    // 异步传输输入数据(Host->Device)
    HANDLE_ERROR(cudaMemcpyAsync(dev_a0, host_a + i, N*sizeof(int), cudaMemcpyHostToDevice, stream0));
    HANDLE_ERROR(cudaMemcpyAsync(dev_b0, host_b + i, N*sizeof(int), cudaMemcpyHostToDevice, stream0));
    //==== 流1处理第i+1个数据块 ====//
    // 异步传输输入数据(Host->Device)
    HANDLE_ERROR(cudaMemcpyAsync(dev_a1, host_a + i + N, N*sizeof(int), cudaMemcpyHostToDevice, stream1));
    HANDLE_ERROR(cudaMemcpyAsync(dev_b1, host_b + i + N, N*sizeof(int), cudaMemcpyHostToDevice, stream1));

    // 启动核函数(流0)
    dim3 block(256);
    dim3 grid((N + block.x -1)/block.x);
    kernel<<<grid, block, 0, stream0>>>(dev_a0, dev_b0, dev_c0);
    // 启动核函数(流1)
    kernel<<<grid, block, 0, stream1>>>(dev_a1, dev_b1, dev_c1);

    // 异步传输输出数据(Device->Host)
    HANDLE_ERROR(cudaMemcpyAsync(host_c + i, dev_c0, N*sizeof(int), cudaMemcpyDeviceToHost, stream0));
    // 异步传输输出数据(Device->Host)
    HANDLE_ERROR(cudaMemcpyAsync(host_c + i + N, dev_c1, N*sizeof(int), cudaMemcpyDeviceToHost, stream1));
}

多GPU

零拷贝

使用 cudaHostAlloc() 分配页锁定内存时使用参数 cudaHostAllocDefault 获得默认的固定内存。使用 cudaHostAllocMapped 参数分配的内存类似,除了用于主机与GPU间内存复制还可以实现:cuda核函数直接访问这种类型主机内存,由于不需要复制到GPU,也称为零拷贝。

#include <stdio.h>
#include <cuda_runtime.h>

// 错误处理宏
#define HANDLE_ERROR(err) (cudaErrorCheck(err, __FILE__, __LINE__))

// 核函数声明
__global__ void dot(float* a, float* b, float* partial_c);

/**************************************
 * 常规CUDA内存分配测试函数
 * size: 要分配的内存大小
 * return: 计算耗时(毫秒)
 **************************************/
float malloc_test(int size) {
    cudaEvent_t start, stop;          // CUDA事件计时器
    float *a, *b, *partial_c;         // 主机端指针
    float *dev_a, *dev_b, *dev_partial_c; // 设备端指针
    float elapsedTime, C = 0;

    // 创建CUDA事件
    HANDLE_ERROR(cudaEventCreate(&start));
    HANDLE_ERROR(cudaEventCreate(&stop));

    // 主机内存分配
    a = (float*)malloc(size * sizeof(float));
    b = (float*)malloc(size * sizeof(float));
    partial_c = (float*)malloc(blockPerGrid * sizeof(float));

    // 设备内存分配
    HANDLE_ERROR(cudaMalloc(&dev_a, size * sizeof(float)));
    HANDLE_ERROR(cudaMalloc(&dev_b, size * sizeof(float)));
    HANDLE_ERROR(cudaMalloc(&dev_partial_c, blockPerGrid * sizeof(float)));

    // 初始化主机数据
    for(int i = 0; i < size; i++) {
        a[i] = 1.0f;
        b[i] = 1.0f;
    }

    // 启动计时器
    HANDLE_ERROR(cudaEventRecord(start, 0));

    // 数据拷贝到设备
    HANDLE_ERROR(cudaMemcpy(dev_a, a, size * sizeof(float), cudaMemcpyHostToDevice));
    HANDLE_ERROR(cudaMemcpy(dev_b, b, size * sizeof(float), cudaMemcpyHostToDevice));

    // 启动核函数
    dot<<<blockPerGrid, threadsPerBlock>>>(dev_a, dev_b, dev_partial_c);

    // 拷贝结果回主机
    HANDLE_ERROR(cudaMemcpy(partial_c, dev_partial_c, blockPerGrid * sizeof(float), cudaMemcpyDeviceToHost));

    // 停止计时
    HANDLE_ERROR(cudaEventRecord(stop, 0));
    HANDLE_ERROR(cudaEventSynchronize(stop));
    HANDLE_ERROR(cudaEventElapsedTime(&elapsedTime, start, stop));

    // CPU端结果汇总
    for(int i = 0; i < blockPerGrid; i++) {
        C += partial_c[i];
    }

    // 释放资源
    free(a); free(b); free(partial_c);
    cudaFree(dev_a); cudaFree(dev_b); cudaFree(dev_partial_c);
    cudaEventDestroy(start); cudaEventDestroy(stop);

    printf("Value calculated: %f\n", C);
    return elapsedTime;
}

/**************************************
 * 零拷贝内存测试函数
 * 使用cudaHostAlloc分配可映射内存
 **************************************/
float cuda_host_alloc_test(int size) {
    cudaEvent_t start, stop;
    float *a, *b, *partial_c;
    float *dev_a, *dev_b, *dev_partial_c;
    float elapsedTime, C = 0;

    // 创建事件
    HANDLE_ERROR(cudaEventCreate(&start));
    HANDLE_ERROR(cudaEventCreate(&stop));

    // 分配可映射的写合并内存
    HANDLE_ERROR(cudaHostAlloc(&a, size * sizeof(float), 
        cudaHostAllocMapped | cudaHostAllocWriteCombined));
    HANDLE_ERROR(cudaHostAlloc(&b, size * sizeof(float), 
        cudaHostAllocMapped | cudaHostAllocWriteCombined));
    HANDLE_ERROR(cudaHostAlloc(&partial_c, blockPerGrid * sizeof(float), 
        cudaHostAllocMapped));

    // 获取设备指针
    HANDLE_ERROR(cudaHostGetDevicePointer(&dev_a, a, 0));
    HANDLE_ERROR(cudaHostGetDevicePointer(&dev_b, b, 0));
    HANDLE_ERROR(cudaHostGetDevicePointer(&dev_partial_c, partial_c, 0));

    // 数据初始化
    for(int i = 0; i < size; i++) {
        a[i] = 1.0f;
        b[i] = 1.0f;
    }

    // 启动核函数
    HANDLE_ERROR(cudaEventRecord(start, 0));
    dot<<<blockPerGrid, threadsPerBlock>>>(dev_a, dev_b, dev_partial_c);
    HANDLE_ERROR(cudaEventRecord(stop, 0));
    HANDLE_ERROR(cudaEventSynchronize(stop));

    // 结果汇总
    for(int i = 0; i < blockPerGrid; i++) {
        C += partial_c[i];
    }

    // 释放资源
    cudaFreeHost(a); cudaFreeHost(b); cudaFreeHost(partial_c);
    cudaEventDestroy(start); cudaEventDestroy(stop);

    printf("Value calculated: %f\n", C);
    return elapsedTime;
}

/**************************************
 * 核函数实现(点积计算)
 * 使用共享内存优化
 **************************************/
__global__ void dot(float* a, float* b, float* partial_c) {
    __shared__ float cache[threadsPerBlock]; // 共享内存缓存
    int tid = threadIdx.x + blockIdx.x * blockDim.x;
    int cacheIndex = threadIdx.x;
    
    // 计算局部和
    float temp = 0;
    while(tid < N) {
        temp += a[tid] * b[tid];
        tid += blockDim.x * gridDim.x;
    }
    
    // 存储到共享内存
    cache[cacheIndex] = temp;
    __syncthreads();
    
    // 归约求和
    int i = blockDim.x / 2;
    while(i != 0) {
        if(cacheIndex < i) {
            cache[cacheIndex] += cache[cacheIndex + i];
        }
        __syncthreads();
        i /= 2;
    }
    
    // 存储块结果
    if(cacheIndex == 0) {
        partial_c[blockIdx.x] = cache[0];
    }
}

/**************************************
 * 主函数
 **************************************/
int main() {
    cudaDeviceProp prop;
    int deviceID;
    cudaGetDevice(&deviceID);
    cudaGetDeviceProperties(&prop, deviceID);

    // 检查设备支持
    if(!prop.canMapHostMemory) {
        printf("Device does not support mapped memory!\n");
        return 0;
    }

    // 设置设备标志
    cudaSetDeviceFlags(cudaDeviceMapHost);

    // 运行测试
    float time1 = malloc_test(N);
    float time2 = cuda_host_alloc_test(N);

    printf("cudaMalloc Time: %.2f ms\n", time1);
    printf("Zero-Copy Time:  %.2f ms\n", time2);
    return 0;
}

程序满足“仅读写一次”的约束条件,使用零拷贝内存时获得性能提升。

使用多个GPU

每个GPU都需要由一个不同的CPU线程控制

#include <stdio.h>
#include <cuda_runtime.h>

// 宏定义(需补充完整错误处理实现)
#define HANDLE_ERROR(err) (cudaErrorCheck(err, __FILE__, __LINE__)) 
#define N (1024 * 1024)        // 默认数据规模
#define threadsPerBlock 256  // 每个块线程数
#define blockPerGrid (N/threadsPerBlock) // 总块数

// 错误处理宏实现示例
#define cudaErrorCheck(err, file, line) \
    if(err != cudaSuccess) { \
        printf("CUDA Error: %s in %s at line %d\n", cudaGetErrorString(err), file, line); \
        exit(EXIT_FAILURE); \
    }

// 线程管理函数原型(需根据实际线程库实现)
void start_thread(pthread_t* thread, void* (*func)(void*), void* arg);
void end_thread(pthread_t thread);

// 数据结构:封装多GPU计算参数
typedef struct DataStruct {
    int deviceID;       // GPU设备ID
    int size;           // 当前设备处理的数据量
    float *a;           // 输入缓冲区A指针
    float *b;           // 输入缓冲区B指针
    float returnValue;  // 计算结果存储
} DataStruct;

/***************************************
 * 核函数:并行点积计算(含共享内存优化)
 * size: 数据量
 * dev_a, dev_b: 设备端输入数组
 * dev_partial_c: 设备端部分和输出
 ***************************************/
__global__ void dot(int size, float* dev_a, float* dev_b, float* dev_partial_c) {
    __shared__ float cache[threadsPerBlock]; // 共享内存缓存
    int tid = threadIdx.x + blockIdx.x * blockDim.x;
    int cacheIndex = threadIdx.x;
    
    float temp = 0;
    // 计算局部点积和
    while(tid < size) {
        temp += dev_a[tid] * dev_b[tid];
        tid += blockDim.x * gridDim.x;
    }
    
    cache[cacheIndex] = temp;
    __syncthreads();
    
    // 归约求和(树形归约法)
    int i = blockDim.x / 2;
    while(i != 0) {
        if(cacheIndex < i) {
            cache[cacheIndex] += cache[cacheIndex + i];
        }
        __syncthreads();
        i /= 2;
    }
    
    if(cacheIndex == 0) {
        dev_partial_c[blockIdx.x] = cache[0];
    }
}

/***************************************
 * GPU计算线程函数
 * pvoidData: 包含计算参数的DataStruct指针
 ***************************************/
void* routine(void* pvoidData) {
    DataStruct* data = (DataStruct*)pvoidData;
    float *a, *b, *partial_c;
    float *dev_a, *dev_b, *dev_partial_c;
    float c = 0;
    int size = data->size;

    // 设置当前线程使用的GPU设备
    HANDLE_ERROR(cudaSetDevice(data->deviceID));

    // 主机内存分配
    a = data->a;
    b = data->b;
    partial_c = (float*)malloc(blockPerGrid * sizeof(float));

    // 设备内存分配
    HANDLE_ERROR(cudaMalloc((void**)&dev_a, size * sizeof(float)));
    HANDLE_ERROR(cudaMalloc((void**)&dev_b, size * sizeof(float)));
    HANDLE_ERROR(cudaMalloc((void**)&dev_partial_c, blockPerGrid * sizeof(float)));

    // 数据传输:主机->设备
    HANDLE_ERROR(cudaMemcpy(dev_a, a, size * sizeof(float), cudaMemcpyHostToDevice));
    HANDLE_ERROR(cudaMemcpy(dev_b, b, size * sizeof(float), cudaMemcpyHostToDevice));

    // 启动核函数计算点积
    dot<<<blockPerGrid, threadsPerBlock>>>(size, dev_a, dev_b, dev_partial_c);

    // 取回部分和结果
    HANDLE_ERROR(cudaMemcpy(partial_c, dev_partial_c, 
                   blockPerGrid * sizeof(float), cudaMemcpyDeviceToHost));

    // CPU端结果汇总
    for(int i = 0; i < blockPerGrid; i++) {
        c += partial_c[i];
    }

    // 资源释放
    free(partial_c);
    cudaFree(dev_a);
    cudaFree(dev_b);
    cudaFree(dev_partial_c);

    data->returnValue = c;  // 存储计算结果
    return 0;
}

/***************************************
 * 主函数
 ***************************************/
int main() {
    int deviceCount;
    float *a, *b;
    
    // 获取CUDA设备数量
    HANDLE_ERROR(cudaGetDeviceCount(&deviceCount));
    if(deviceCount < 2) {
        printf("需要至少2个CUDA设备,当前检测到%d个\n", deviceCount);
        return -1;
    }

    // 分配主机内存
    a = (float*)malloc(N * sizeof(float));
    b = (float*)malloc(N * sizeof(float));
    for(int i = 0; i < N; i++) {
        a[i] = 1.0f;
        b[i] = 1.0f;
    }

    // 初始化多GPU任务结构
    DataStruct data[2];
    data[0].deviceID = 0;          // 第一个GPU
    data[0].size = N/2;            // 分配半数数据
    data[0].a = a;                 // 指向数组起始位置
    data[0].b = b;
    
    data[1].deviceID = 1;          // 第二个GPU
    data[1].size = N/2;
    data[1].a = a + N/2;           // 指向后半段数据
    data[1].b = b + N/2;

    // 创建计算线程(假设start_thread为自定义线程创建函数)
    pthread_t thread;
    start_thread(&thread, routine, &data[0]);  // 创建线程处理第一个GPU
    
    routine(&data[1]);              // 主线程处理第二个GPU
    
    end_thread(thread);            // 等待子线程完成

    // 汇总并显示结果
    printf("计算结果: %f\n", data[0].returnValue + data[1].returnValue);

    // 资源释放
    free(a);
    free(b);
    return 0;
}

可移动的固定内存

通过可移动的固定内存使多个GPU共享固定内存。

相关API

/*
内存分配优化
cudaHostAllocPortable:使固定内存可在不同设备/线程间共享
cudaHostAllocMapped:创建零拷贝内存,GPU可直接访问
*/
cudaHostAlloc(&a, N*sizeof(float), cudaHostAllocPortable | cudaHostAllocMapped);

/*
设备指针获取
将主机固定内存映射到设备地址空间
多GPU环境下需确保已设置当前设备
*/
cudaHostGetDevicePointer(&dev_a, data->a, 0);

/*
设备上下文管理
避免重复设置设备0的上下文
确保每个线程管理自己的设备上下文
*/
if(data->deviceID != 0) {
    cudaSetDevice(data->deviceID);
    cudaSetDeviceFlags(cudaDeviceMapHost);
}

/*
异步操作支持
可移动内存支持异步内存拷贝(cudaMemcpyAsync)
需配合CUDA流实现计算/传输重叠
*/
dot<<<..., cudaStreamNonBlocking>>>(); // 可结合流使用

性能优化:​

  1. ​内存对齐​​:使用cudaHostAllocWriteCombined提升写入性能
  2. ​负载均衡​​:根据GPU算力动态分配数据量
  3. ​流式处理​​:使用多个CUDA流隐藏内存延迟
  4. ​统一寻址​​:在Pascal+架构启用cudaDeviceMapHost + cudaHostAllocMapped
#include <stdio.h>
#include <cuda_runtime.h>
#include <pthread.h>

#define HANDLE_ERROR(err) (cudaErrorCheck(err, __FILE__, __LINE__))
#define N (1024 * 1024)       // 数据规模
#define threadsPerBlock 256  // 块内线程数
#define blockPerGrid (N/threadsPerBlock)

// 数据结构封装(支持多GPU参数传递)
typedef struct {
    int deviceID;        // GPU设备ID
    size_t offset;       // 数据偏移量
    size_t size;         // 处理数据量
    float* a;            // 输入数组A(可移动固定内存)
    float* b;            // 输入数组B(可移动固定内存)
    float returnValue;   // 计算结果存储
} DataStruct;

// 核函数:带共享内存优化的点积计算
__global__ void dot(float* a, float* b, float* partial_c, int size) {
    __shared__ float cache[threadsPerBlock];
    int tid = threadIdx.x + blockIdx.x * blockDim.x;
    int cacheIndex = threadIdx.x;
    
    float temp = 0;
    while(tid < size) {
        temp += a[tid] * b[tid];
        tid += blockDim.x * gridDim.x;
    }
    
    cache[cacheIndex] = temp;
    __syncthreads();
    
    // 归约求和优化
    for(int i = blockDim.x/2; i>0; i>>=1) {
        if(cacheIndex < i)
            cache[cacheIndex] += cache[cacheIndex + i];
        __syncthreads();
    }
    
    if(cacheIndex == 0)
        partial_c[blockIdx.x] = cache[0];
}

// GPU计算线程函数(关键修改点)
void* routine(void* pvoidData) {
    DataStruct* data = (DataStruct*)pvoidData;
    float *partial_c, *dev_partial_c;
    float *dev_a, *dev_b;
    
    // 设备上下文设置(避免重复设置)
    if(data->deviceID != 0) { // 主线程已设置设备0
        HANDLE_ERROR(cudaSetDevice(data->deviceID));
        HANDLE_ERROR(cudaSetDeviceFlags(cudaDeviceMapHost));
    }
    
    // 获取设备指针(零拷贝关键)
    HANDLE_ERROR(cudaHostGetDevicePointer(&dev_a, data->a, 0));
    HANDLE_ERROR(cudaHostGetDevicePointer(&dev_b, data->b, 0));
    
    // 分配临时设备内存
    HANDLE_ERROR(cudaMalloc(&dev_partial_c, blockPerGrid * sizeof(float)));
    partial_c = (float*)malloc(blockPerGrid * sizeof(float));
    
    // 启动核函数
    dot<<<blockPerGrid, threadsPerBlock>>>(dev_a + data->offset, 
                                         dev_b + data->offset, 
                                         dev_partial_c, 
                                         data->size);
    
    // 取回部分和
    HANDLE_ERROR(cudaMemcpy(partial_c, dev_partial_c, 
                          blockPerGrid * sizeof(float),
                          cudaMemcpyDeviceToHost));
    
    // CPU端结果汇总
    float c = 0;
    for(int i=0; i<blockPerGrid; i++)
        c += partial_c[i];
    
    // 资源释放
    free(partial_c);
    HANDLE_ERROR(cudaFree(dev_partial_c));
    
    data->returnValue = c;
    return 0;
}

int main() {
    int deviceCount;
    float *a, *b;
    
    // 1. 设备兼容性检查
    HANDLE_ERROR(cudaGetDeviceCount(&deviceCount));
    if(deviceCount < 2) {
        printf("需要至少2个支持CUDA 1.0+的设备,当前检测到%d个\n", deviceCount);
        return -1;
    }
    
    // 2. 设备属性验证(支持内存映射)
    cudaDeviceProp prop;
    for(int i=0; i<2; i++) {
        HANDLE_ERROR(cudaGetDeviceProperties(&prop, i));
        if(!prop.canMapHostMemory) {
            printf("设备%d不支持内存映射\n", i);
            return -1;
        }
    }
    
    // 3. 分配可移动固定内存(关键修改)
    HANDLE_ERROR(cudaSetDevice(0)); // 主线程设置设备0
    HANDLE_ERROR(cudaSetDeviceFlags(cudaDeviceMapHost));
    HANDLE_ERROR(cudaHostAlloc(&a, N*sizeof(float), 
                             cudaHostAllocPortable | cudaHostAllocMapped));
    HANDLE_ERROR(cudaHostAlloc(&b, N*sizeof(float), 
                             cudaHostAllocPortable | cudaHostAllocMapped));
    
    // 初始化数据
    for(int i=0; i<N; i++) {
        a[i] = 1.0f;
        b[i] = 1.0f;
    }
    
    // 4. 任务分配(多GPU数据分割)
    DataStruct data[2] = {
        {0, 0, N/2, a, b, 0},    // GPU0处理前半数据
        {1, N/2, N/2, a, b, 0}   // GPU1处理后半数据
    };
    
    // 5. 创建计算线程
    pthread_t thread;
    pthread_create(&thread, NULL, routine, &data[0]); // 子线程处理GPU0
    routine(&data[1]);                                 // 主线程处理GPU1
    pthread_join(thread, NULL);
    
    // 6. 结果汇总
    printf("计算结果: %f\n", data[0].returnValue + data[1].returnValue);
    
    // 7. 释放可移动固定内存
    HANDLE_ERROR(cudaFreeHost(a));
    HANDLE_ERROR(cudaFreeHost(b));
    
    return 0;
}

通过可移动固定内存解决了多GPU环境下的两个关键问题:

  1. 跨线程内存访问性能下降
  2. 异步操作失败问题
posted @ 2025-05-02 21:04  某某人8265  阅读(155)  评论(0)    收藏  举报