CUDA矩阵元素求和

CUDA矩阵元素求和


在使用cuda 做图像高性能处理的时候,会很频繁的使用到矩阵的运算,于是打算做成一个相对完整的矩阵运算库,一旦使用调用即可,便再不用临时去写矩阵运算的规则和方法了。矩阵的加 减 乘 转置自然简单,在cuda中,矩阵的每个元素都对应着一个线程,于是矩阵加法转换为线程中的加法,减法变成了线程,乘法,转置也可以转化成类似的思路。

当我写到求一个矩阵所有元素的和的时候,顺着思维惯性写成了这样的程序

__global__ void matMixKernel(int *Result, int **A){



    //输入参数中,A是要求和的矩阵,Result 是用来保存结果的int指针



    int i = blockIdx.x * blockDim.x + threadIdx.x;

    int j = blockIdx.y * blockDim.y + threadIdx.y;





    Result += A[i][j];





}

抽象一下上面这个程序,仔细一想,自己都笑喷了。

把矩阵想象成一个仓库,大大小小的区域 存放着各种各样的货物,这些货物都用来建房子,

然后每个线程都是一个工人,每个工人管理了一块独立的区域,矩阵的元素求和,就变成了一个很生动的问题
How to build the house?

所以上面的这段代码做得就是:

让所有的工人抱着自己的材料来到要建房子的空地,同时把材料丢下,然后。。。说好的房子就变成了垃圾场,哈哈

不禁想起小学时候老师经常挂在嘴边的问题,十个工人建房子需要一年,四十个工人呢?大家一同喊道,一季度,那么3650个工人呢?又一同喊道,一天。

果真用一天的时间去让3650个人建好一栋房子的话,在五楼干活的那部分人一定会死命的叫唤:老板!四楼水泥还没干呢!



———————————————————分割线———————————————————




在生活中,上面的故事理所应该被当成了笑话,但是在cuda或者是其他种类的并行运算中,类似的笑话经常发生。

把一个有顺序的过程使用并行程序实现,通常被认为是不可能实现的事情,但是稍微转换下思维,并行计算也是可以有顺序存在的。

还是拿刚刚的那个例子,在建房子的过程中,搅和水泥和打地基是可以同时干的两件事情,在这样的子过程中,就是并行计算发挥作用的时候了,我们可以叫100个人同时搅和水泥,叫20人开机器同时打地基,等到地基打好了,水泥也好了,于是乎直接把水泥倒入地基,这样肯定比120个人同时打地基,再搅拌水泥,再倒入水泥速度快。

解决这个问题的方法就是分小队,在cuda中解决类似的问题也是一样,并不是所有线程都需要参与计算,我们把线程分成三种,一种手里拿着加数,一个手里拿着被加数,然后一种手里拿着结果,整个过程也分成两部完成,首先拿着加数的和拿着被加数的碰面做加法运算,算好后手里拿结果的排好队,第二步,把所有手里的结果扔给排头的那个人,然后让那个人拿着总结果来汇报就行了。

是否整个过程行云流水?下面是代码分析,有几个地方我还是改了很久才调试通的

__global__ void block_sum(const int *input,

    int *per_block_results,

    const size_t n)

{

    extern __shared__ int sdata[];



    unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;



    // load input into __shared__ memory //一个线程负责把一个元素从全局内存载入到共享内存

    int x = 0;

    if (i < n)

    {

        x = input[i];

    }

    sdata[threadIdx.x] = x;

    __syncthreads();//等待所有线程把自己负责的元素载入到共享内存



    // contiguous range pattern//块内进行合并操作,每次合并变为一半.注意threadIdx.x是块内的偏移,上面算出的i是全局的偏移。

    for (int offset = blockDim.x / 2;

        offset > 0;

        offset >>= 1)

    {

        if (threadIdx.x < offset)//控制只有某些线程才进行操作。

        {

            // add a partial sum upstream to our own

            sdata[threadIdx.x] += sdata[threadIdx.x + offset];

        }



        // wait until all threads in the block have

        // updated their partial sums

        __syncthreads();

    }



    // thread 0 writes the final result//每个块的线程0负责存放块内求和的结果

    if (threadIdx.x == 0)

    {

        per_block_results[blockIdx.x] = sdata[0];

    }

}

//调用方法



cudaError_t matMix(int matA[ROW][COL], unsigned int *Result){



    cudaError_t cudaStatus;



    /***/

    unsigned int num_elements = ROW*COL;

    int h_input[ROW*COL];

    for (int i = 0;i < ROW*COL;i++){



        h_input[i] = matA[i/COL][i%COL];



    }



    //分配内存

    int *d_input = 0;

    cudaMalloc((void**)&d_input, sizeof(int)* num_elements);

    cudaMemcpy(d_input, &h_input[0], sizeof(int)* num_elements, cudaMemcpyHostToDevice);



    const size_t block_size = 512;//线程块的大小。目前有些gpu的线程块最大为512,有些为1024.

    const size_t num_blocks = (num_elements / block_size) + ((num_elements%block_size) ? 1 : 0);





    int *d_partial_sums_and_total = 0;//一个线程块一个和,另外加一个元素,存放所有线程块的和。

    cudaMalloc((void**)&d_partial_sums_and_total, sizeof(int)* (num_blocks + 1));



//把每个线程块的和求出来

    block_sum <<<num_blocks, block_size, block_size *sizeof(int)>>>(d_input, d_partial_sums_and_total, num_elements);



    cudaStatus = cudaThreadSynchronize();





    //再次用一个线程块把上一步的结果求和。

    //注意这里有个限制,上一步线程块的数量,必须不大于一个线程块线程的最大数量,因为这一步得把上一步的结果放在一个线程块操作。

    //即num_blocks不能大于线程块的最大线程数量。

    block_sum << <1, num_blocks, num_blocks * sizeof(int) >> >(d_partial_sums_and_total, d_partial_sums_and_total + num_blocks, num_blocks);

    cudaStatus = cudaThreadSynchronize();





    // copy the result back to the host

    int device_result = 0;

    cudaMemcpy(&device_result, d_partial_sums_and_total + num_blocks, sizeof(int), cudaMemcpyDeviceToHost);



    *Result = device_result;



    if (cudaStatus != cudaSuccess){



        cout <<cudaStatus << endl;



    }



    // deallocate device memory

    cudaFree(d_input);

    cudaFree(d_partial_sums_and_total);









    return cudaStatus;

}

于是调试,搞定!

一个简单的 CUDA 向量元素求和算法如下: 1. 将输入向量拷贝到设备(GPU)内存中。 2. 在设备上分配用于输出的内存空间。 3. 在设备上启动一个多个线程的 GPU 核心,每个线程处理向量中的若干个元素。 4. 每个线程计算它所处理的元素的和,并将结果存储在共享内存中。 5. 使用原子操作将每个线程的局部和加入到全局和中。 6. 将全局和从设备内存拷贝回主机(CPU)内存中。 下面是一个简单的 CUDA C 实现: ```cuda __global__ void sum_kernel(float* input, float* output, int n) { __shared__ float sdata[256]; int tid = threadIdx.x; int i = blockIdx.x * blockDim.x + threadIdx.x; sdata[tid] = (i < n) ? input[i] : 0; __syncthreads(); for (int s = 1; s < blockDim.x; s *= 2) { if (tid % (2*s) == 0) { sdata[tid] += sdata[tid + s]; } __syncthreads(); } if (tid == 0) { output[blockIdx.x] = sdata[0]; } } float cuda_sum(float* input, int n) { float* d_input, *d_output; cudaMalloc(&d_input, n*sizeof(float)); cudaMalloc(&d_output, 256*sizeof(float)); cudaMemcpy(d_input, input, n*sizeof(float), cudaMemcpyHostToDevice); int threads_per_block = 256; int blocks_per_grid = (n + threads_per_block - 1)/threads_per_block; sum_kernel<<<blocks_per_grid, threads_per_block>>>(d_input, d_output, n); float* output = (float*) malloc(blocks_per_grid*sizeof(float)); cudaMemcpy(output, d_output, blocks_per_grid*sizeof(float), cudaMemcpyDeviceToHost); float sum = 0; for (int i = 0; i < blocks_per_grid; i++) { sum += output[i]; } cudaFree(d_input); cudaFree(d_output); free(output); return sum; } ``` 该算法使用了线程块和共享内存来并行计算向量元素的和。每个线程块处理一个固定大小的子向量,每个线程计算它所处理的元素的和,并将结果存储在共享内存中。然后,使用原子操作将每个线程的局部和加入到全局和中。最后,将全局和从设备内存拷贝回主机内存中并返回。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值