cuda中当数组数大于线程数的处理方法

本文介绍了一种在CUDA中使用不同线程索引方法实现图像像素反转的算法。通过对比CPU和GPU的计算结果,验证了CUDA网格尺寸计算函数的有效性和正确性。代码示例展示了如何在CUDA中分配设备内存,调用内核函数进行并行计算,并将结果复制回主机。

参考stackoverflow一篇帖子的处理方法:https://stackoverflow.com/questions/26913683/different-way-to-index-threads-in-cuda-c

代码中cuda_gridsize函数参考yolo。

代码如下:

#include "cuda_runtime.h"
#include "device_launch_parameters.h"

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <ctime>

using namespace std;
#define BLOCK 512

dim3 cuda_gridsize(size_t n){
    size_t k = (n - 1) / BLOCK + 1;
    unsigned int x = k;
    unsigned int y = 1;
    if (x > 65535){
        x = ceil(sqrt(k));
        y = (n - 1) / (x*BLOCK) + 1;
    }
    dim3 d = { x, y, 1 };
    //printf("%ld %ld %ld %ld\n", n, x, y, x*y*BLOCK);
    return d;
}

__global__ void gpuCalc(unsigned char *img,long H,long W)
{
    long threadId_2D = threadIdx.x + threadIdx.y*blockDim.x;
    long blockId_2D = blockIdx.x + blockIdx.y*gridDim.x;
    long i = threadId_2D + (blockDim.x*blockDim.y)*blockId_2D;
    
    //另一种索引方式
    //long i = (gridDim.x*blockDim.x)*(threadIdx.y + blockDim.y*blockIdx.y) + (threadIdx.x + blockDim.x*blockIdx.x);

    while (i < H*W){
        img[i] = 255 - img[i];
        i += (gridDim.x*blockDim.x)*(gridDim.y*blockDim.y);
    }
}

void addWithCuda(unsigned char *img, long H,long W)
{
    unsigned char *dev_a = 0;

    cudaSetDevice(0);

    cudaMalloc((void**)&dev_a, H*W * sizeof(unsigned char));
    cudaMemcpy(dev_a, img, H*W * sizeof(unsigned char), cudaMemcpyHostToDevice);

    gpuCalc<<<cuda_gridsize(H*W),BLOCK>> >(dev_a, H, W);

    cudaMemcpy(img, dev_a, H*W * sizeof(unsigned char), cudaMemcpyDeviceToHost);
    cudaFree(dev_a);

    cudaGetLastError();
}

void cpuCalc(unsigned char *img,long W, long H)
{
    for (long i = 0; i < H*W; i++)
        img[i] = 255 - img[i];
}

int main()
{
    long W = 20000;
    long H = 20000;

    unsigned char *img = new unsigned char[W*H];
    unsigned char *cmp = new unsigned char[W*H];

    for (long i = 0; i < H*W; i++)
        img[i] = rand() % 100;

    memcpy(cmp, img, H*W);

    cpuCalc(img, W, H);
    printf("cpu calc end\n");

    addWithCuda(img, W,H);
    printf("gpu calc end\n");

    bool flag = true;
    for (long i = 0; i < H*W; i++)
    {
        if (img[i] != cmp[i])
        {
            printf("no pass\n");
            flag = false;
            break;
        }
    }
    if (flag)
        printf("pass");

    delete[] cmp;
    delete[] img;
    getchar();

    return 0;
}

转载于:https://www.cnblogs.com/tiandsp/p/9460130.html

<think>好的,用户现在问的是如何使用CUDA申请图像数组的显存,并将这个数组作为核函的输入。之前的问题是关于如何查看cv::Mat的字节,现在转向了CUDA的内存管理和核函调用,看来用户可能在进行图像处理相关的GPU加速开发。 首先,我需要回忆CUDA的基本内存管理步骤。用户需要知道如何分配显存,将据从主机内存复制到显存,然后在核函中使用,最后释放显存。这些都是CUDA编程的基础,但结合图像处理可能会有一些特别需要注意的地方,比如据布局、对齐或者是否连续存储。 接下来,用户提到的是图像数组。图像通常是二维的,可能还有多个通道,比如RGB图像有三个通道。所以在申请显存时,需要考虑二维数组的分配,而不仅仅是线性的一维数组。这里可能需要使用cudaMallocPitch或者cudaMalloc3D来确保内存对齐,提高访问效率。尤其是当图像的宽度不是对齐到特定字节时,使用pitch可以避免性能问题。 然后,用户需要将数组作为核函的输入。核函的参需要是设备指针,所以需要正确地将分配的设备内存指针传递给核函。在核函内部,如何索引这个二维数组也是一个关键点。使用pitch分配的内存,访问时需要通过计算行指针的偏移,确保每个线程正确访问对应的像素。 另外,用户可能在使用OpenCV的cv::Mat结构,所以需要考虑如何将cv::Mat的据复制到设备内存中。需要确认cv::Mat的据是否是连续的,如果是ROI或者其他非连续的情况,可能需要特殊处理,比如先复制连续的据到设备,或者在核函中调整访问方式。 还需要注意据类型和通道。例如,uchar类型的单通道图像和uchar3的三通道图像在内存中的排列方式不同,核函中访问的方式也会有差异。用户可能需要根据具体的图像格式来调整核函中的内存访问逻辑。 此外,用户可能会忽略错误处理,比如检查cudaMalloc的返回值是否为cudaSuccess,或者在内存复制后检查是否成功。这些虽然基础,但在实际开发中非常重要,可以避免很多难以调试的问题。 最后,用户可能需要一个完整的示例代码,展示从分配显存、复制据、调用核函到释放显存的整个流程。示例代码需要清晰,注释明确,特别是如何计算索引和处理不同通道的情况。 总结下来,回答的结构应该包括显存分配的步骤、据复制、核函传递、索引计算示例以及资源释放,同时提醒注意事项如错误检查、据类型对齐等。可能还需要比较不同分配函(如cudaMalloc vs cudaMallocPitch)的适用场景,帮助用户根据具体情况选择合适的方法。</think>在CUDA中为图像数组申请显存并传递到核函,需遵循以下步骤: --- ### **1. 显存分配** 根据图像据的维度与类型,选择适合的分配方式: #### **方式1:一维显存(适合连续存储的灰度图)** ```cpp uchar* d_img; // 设备指针(假设为8位单通道) size_t imgSize = rows * cols * sizeof(uchar); // 总字节 cudaMalloc(&d_img, imgSize); // 申请显存 ``` #### **方式2:二维Pitch显存(推荐用于宽图像,自动对齐内存)** ```cpp uchar* d_img; size_t pitch; // 实际分配的每行字节(含填充) cudaMallocPitch(&d_img, &pitch, cols * sizeof(uchar), rows); // cols为图像宽度(像素) ``` #### **方式3:三维显存(多通道或体积据)** ```cpp cudaExtent extent = make_cudaExtent(width, height, depth); // 宽度、高度、深度 cudaPitchedPtr d_img; // 包含指针、pitch、尺寸信息 cudaMalloc3D(&d_img, extent); ``` --- ### **2. 据拷贝到显存** 使用`cudaMemcpy`或带Pitch的函: ```cpp // 一维拷贝 cudaMemcpy(d_img, host_img.data, imgSize, cudaMemcpyHostToDevice); // 二维拷贝(带Pitch) cudaMemcpy2D( d_img, pitch, // 目标地址及pitch host_img.data, host_img.step, // 源地址及行步长 cols * sizeof(uchar), rows, // 拷贝区域的宽度(字节)、高度 cudaMemcpyHostToDevice ); ``` --- ### **3. 核函输入设计** 在核函中通过设备指针访问图像据: ```cpp // 核函示例:将图像反色(单通道) __global__ void invertImage(uchar* d_img, int rows, int cols, size_t pitch) { int x = blockIdx.x * blockDim.x + threadIdx.x; // 列索引 int y = blockIdx.y * blockDim.y + threadIdx.y; // 行索引 if (x < cols && y < rows) { // 计算带Pitch的偏移(y行起始地址 + x字节偏移) uchar* row_ptr = (uchar*)((char*)d_img + y * pitch); row_ptr[x] = 255 - row_ptr[x]; // 反色操作 } } ``` --- ### **4. 核函调用** 配置线程块和网格维度后启动核函: ```cpp dim3 blockSize(16, 16); // 每个线程块16x16线程 dim3 gridSize((cols + blockSize.x - 1) / blockSize.x, (rows + blockSize.y - 1) / blockSize.y); invertImage<<<gridSize, blockSize>>>(d_img, rows, cols, pitch); cudaDeviceSynchronize(); // 等待核函完成 ``` --- ### **5. 显存释放** ```cpp cudaFree(d_img); // 释放显存 ``` --- ### **完整代码示例(处理单通道灰度图)** ```cpp #include <opencv2/opencv.hpp> #include <cuda_runtime.h> // 核函:图像反色 __global__ void invertImage(uchar* d_img, int rows, int cols, size_t pitch) { int x = blockIdx.x * blockDim.x + threadIdx.x; int y = blockIdx.y * blockDim.y + threadIdx.y; if (x < cols && y < rows) { uchar* row = (uchar*)((char*)d_img + y * pitch); row[x] = 255 - row[x]; } } int main() { cv::Mat img = cv::imread("input.jpg", cv::IMREAD_GRAYSCALE); // 读取为单通道 uchar* d_img; size_t pitch; // 分配带Pitch的显存 cudaMallocPitch(&d_img, &pitch, img.cols * sizeof(uchar), img.rows); // 拷贝据到显存 cudaMemcpy2D(d_img, pitch, img.data, img.step, img.cols * sizeof(uchar), img.rows, cudaMemcpyHostToDevice); // 启动核函 dim3 block(16, 16); dim3 grid((img.cols + block.x - 1) / block.x, (img.rows + block.y - 1) / block.y); invertImage<<<grid, block>>>(d_img, img.rows, img.cols, pitch); // 拷贝结果回主机 cv::Mat result(img.size(), CV_8UC1); cudaMemcpy2D(result.data, result.step, d_img, pitch, img.cols * sizeof(uchar), img.rows, cudaMemcpyDeviceToHost); cudaFree(d_img); cv::imwrite("output.jpg", result); return 0; } ``` --- ### **关键注意事项** 1. **内存对齐**: - 使用`cudaMallocPitch`避免非对齐访问导致的性能下降。 - `pitch`值可能大于`cols * sizeof(datatype)`(因硬件对齐要求)。 2. **多通道图像**: - 对于RGB等多通道图像,调整核函索引逻辑(例如`col * channels + channel`)。 3. **错误检查**: - 在每次CUDA API调用后添加`cudaError_t err = cudaGetLastError()`检查错误。 4. **异步性**: - 核函执行是异步的,需用`cudaDeviceSynchronize()`确保完成后再读取据。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值