基于C++与CUDA的N卡GPU并行程序——二维矩阵索引、按行求和、矩阵乘法

二维矩阵索引

  之前的程序只涉及到了一维数组的操作,那么对于二维矩阵如何操作呢?假设有一个 M M M N N N列的二维矩阵 A A A,首先在C++中为了能够使用二维索引,需要定义长度为 M ∗ N M*N MN的一级数据指针 f l o a t   ∗ a _ d a t a float\ *a\_data float a_data保存所有矩阵元素,然后定义长度为 M M M的二级指针 f l o a t   ∗ ∗ a float\ **a float a保存矩阵中每行的起始位置,最后使用 a [ i ] [ j ] a[i][j] a[i][j]进行二维矩阵的索引.
  在CPU主机端使用malloc即可创建内存空间,而在GPU中可以使用cudaMalloc创建内存空间,但是这时是将二维矩阵元素进行一维展开的线性内存,如果对矩阵的每行元素分别对待并进行每行的内存对齐,那么访问效率将会更高.cuda中也有对此进行优化的专门函数,也就是使用cudaMallocPitch来创建二维内存空间,使用时涉及到参数pitch表示每行的内存大小N*sizeof(float),其所分配的内存保证每行是128的整数倍,不够的就多分配进行补齐.所以在定义二级指针 ∗ a *a a时,不是

a[rowIndex] = a_data + rowIndex * N;

因为这时的每行个数也就是矩阵列数不是N,而是补齐之后的 p i t c h / s i z e o f ( f l o a t ) pitch/sizeof(float) pitch/sizeof(float),所以是

a[rowIndex] = a_data + rowIndex * pitch / sizeof(float);

  然后在GPU设备端的内核函数中就能使用 a [ i ] [ j ] a[i][j] a[i][j]的二维索引进行操作了,最后涉及到数据的拷贝则使用cudaMemcpy2D函数,函数中需要指定主机端和设备端的数据指针的每行内存大小,最后完整代码示例如下.

#include<iostream>
#include"book.h"
#include<cuda_runtime.h>
#include<device_launch_parameters.h>
#include"lock.h"

__global__ void testMat( float **a, int rows, int cols ){
    
    
    for(int tid_y = threadIdx.y + blockDim.y * blockIdx.y;tid_y < rows;
                                      tid_y += blockDim.y * gridDim.y){
    
    
        for(int tid_x = threadIdx.x + blockDim.x * blockIdx.x;tid_x < cols;
                                          tid_x += blockDim.x * gridDim.x){
    
    
            a[tid_y][tid_x] += 0.1;
        }
    }
}

int main( void ){
    
    
    int rows = 8;//定义矩阵行数M
    int cols = 4;//定义矩阵行数N
    //创建分配主机端和设备端的内存空间
    float **host_a,**dev_a;
    host_a = (float**)malloc(rows*sizeof(float*));
    HANDLE_ERROR( cudaMalloc( (void**)&dev_a,rows*sizeof(float*) ) );
    float *host_a_data,*dev_a_data;size_t pitch;
    host_a_data = (float*)malloc(rows*cols*sizeof(float));
    HANDLE_ERROR( cudaMallocPitch( &dev_a_data, &pitch, cols*sizeof(float), rows ) );
    HANDLE_ERROR( cudaMemset2D( dev_a_data, pitch, 0, cols*sizeof(float), rows ) );
    for(int r = 0;r < rows;r++){
    
    
        host_a[r] = dev_a_data + r*pitch / sizeof(float);
    }

    //主机端填充矩阵元素
    for(int i = 0;i < rows*cols;i++){
    
    
        host_a_data[i] = i;
    }

    //主机端拷贝矩阵到设备端
    HANDLE_ERROR( cudaMemcpy( dev_a, host_a, rows*sizeof(float*), cudaMemcpyHostToDevice ) );
    HANDLE_ERROR( cudaMemcpy2D( dev_a_data, pitch, host_a_data, cols*sizeof(float), 
                                cols*sizeof(float), rows, cudaMemcpyHostToDevice) );

    //设置线程网格和线程块的排布
    dim3 threads_rect(2,2);
    dim3 blocks_rect(2,2);

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

    //执行设备端的内核函数
    testMat<<<blocks_rect,threads_rect>>>(dev_a, rows, cols);

    //设备端拷贝矩阵到主机端
    HANDLE_ERROR( cudaMemcpy2D( host_a_data, cols*sizeof(float), dev_a_data, pitch, 
                                cols*sizeof(float), rows, cudaMemcpyDeviceToHost) );

    //获取结束时间,并显示计时结果
    HANDLE_ERROR( cudaEventRecord( stop, 0 ) );
    HANDLE_ERROR( cudaEventSynchronize( stop ) );
    HANDLE_ERROR( cudaEventElapsedTime( &elapsedTime, start, stop ) );
    printf( "Time to generate:  %3.2f ms\n", elapsedTime );
    HANDLE_ERROR( cudaEventDestroy( start ) );
    HANDLE_ERROR( cudaEventDestroy( stop ) );

    //打印主机端矩阵数据中的计算结果
    for(int i = 0;i < rows*cols;i++){
    
    
        printf("%4.4f ",host_a_data[i]);
        if(i%cols == cols-1)
            printf("\n");
    }

    //释放GPU设备端内存
    HANDLE_ERROR( cudaFree( dev_a ) );
    HANDLE_ERROR( cudaFree( dev_a_data ) );
    //释放CPU主机端内存
    free( host_a );
    free( host_a_data );
    
}

二维矩阵按行求和

  对二维矩阵进行按行求和,也就是每一行单独求出一个和,最后有M个求和结果并以数组形式返回结果.首先需要设置线程网格中线程块的排布方式,这里有两种方式,第一种是M行1列的线程块,第二种是M行L列的线程块,L可取16或其它数字.第一种方式中,缺点是每行求和时的线程数量最大为1024,但是优点是更简单不需要进行多个线程块之间的原子累加操作.第二种方式中,优点是处理每行的线程数量可以远超1024,但是缺点是更复杂一点,需要进行原子操作,而原子操作的效率是很低的.具体如何使用,可以根据实际的数据,尝试不同的方式以确定最佳设置.最后完整代码示例如下.

#include<iostream>
#include"book.h"
#include<cuda_runtime.h>
#include<device_launch_parameters.h>
#include"lock.h"

__global__ void testMat_1( float **a, int rows, int cols ){
    
    
    /*对所有矩阵元素进行加0.1*/
    for(int tid_y = threadIdx.y + blockDim.y * blockIdx.y;tid_y < rows;
                                      tid_y += blockDim.y * gridDim.y){
    
    
        for(int tid_x = threadIdx.x + blockDim.x * blockIdx.x;tid_x < cols;
                                          tid_x += blockDim.x * gridDim.x){
    
    
            a[tid_y][tid_x] += 0.1;
        }
    }
}

__global__ void testMat_2( float **a,float *result, int rows, int cols ){
    
    
    /*对矩阵的每行求和,每个block线程块处理一行*/
    __shared__ float cache[1024];
    int cacheIndex = threadIdx.x;
    float temp = 0;
    int tid_y = blockIdx.x;
    for(int tid_x = threadIdx.x;tid_x < cols;tid_x += blockDim.x){
    
    
        //a[tid_y][tid_x] += 0.1;
        temp += a[tid_y][tid_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)
        result[tid_y] = cache[0];
}

__global__ void testMat_3( Lock lock,float **a,float *result, int rows, int cols ){
    
    
    /*对矩阵的每行求和,block线程块组成M行32列的线程网格*/
    __shared__ float cache[1024];
    int cacheIndex = threadIdx.x;
    float temp = 0;
    int tid_y = blockIdx.y;
    for(int tid_x = threadIdx.x + blockDim.x * blockIdx.x;tid_x < cols;
                                      tid_x += blockDim.x * gridDim.x){
    
    
    //for(int tid_x = threadIdx.x;tid_x < cols;tid_x += blockDim.x){
    
    
        //a[tid_y][tid_x] += 0.1;
        temp += a[tid_y][tid_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){
    
    
        lock.lock();
        result[tid_y] += cache[0];
        lock.unlock();
    }
}

int main( void ){
    
    
    int rows = 1024;//定义矩阵行数M
    int cols = 131072;//定义矩阵列数N
    //创建分配主机端和设备端的内存空间
    float **host_a,**dev_a;
    host_a = (float**)malloc(rows*sizeof(float*));
    HANDLE_ERROR( cudaMalloc( (void**)&dev_a,rows*sizeof(float*) ) );
    float *host_a_data,*dev_a_data,*host_result,*dev_result;size_t pitch;
    host_a_data = (float*)malloc(rows*cols*sizeof(float));
    host_result = (float*)malloc(rows*sizeof(float));
    HANDLE_ERROR( cudaMallocPitch( &dev_a_data, &pitch, cols*sizeof(float), rows ) );
    HANDLE_ERROR( cudaMemset2D( dev_a_data, pitch, 0, cols*sizeof(float), rows ) );
    HANDLE_ERROR( cudaMalloc( (void**)&dev_result,rows*sizeof(float) ) );
    for(int r = 0;r < rows;r++){
    
    
        host_a[r] = dev_a_data + r*pitch / sizeof(float);
        //host_a[r] = (float*)((char*)dev_a_data + r*pitch);
    }

    //主机端填充矩阵元素
    for(int i = 0;i < rows*cols;i++){
    
    
        host_a_data[i] = 0.01;
    }

    //主机端拷贝矩阵到设备端
    HANDLE_ERROR( cudaMemcpy( dev_a, host_a, rows*sizeof(float*), cudaMemcpyHostToDevice ) );
    HANDLE_ERROR( cudaMemcpy2D( dev_a_data, pitch, host_a_data, cols*sizeof(float), 
                                cols*sizeof(float), rows, cudaMemcpyHostToDevice) );

    //设置线程网格和线程块的排布
    dim3 threads_rect(2,2);
    dim3 blocks_rect(2,2);

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

    //执行设备端的内核函数
    //testMat_1<<<blocks_rect,threads_rect>>>(dev_a, rows, cols);
    testMat_2<<<rows,1024>>>(dev_a,dev_result, rows, cols);
    //dim3 blocks_rect_2(1,rows);
    //Lock lock;
    //testMat_3<<<blocks_rect_2,1024>>>(lock, dev_a, dev_result, rows, cols);

    //设备端拷贝矩阵到主机端
    //HANDLE_ERROR( cudaMemcpy2D( host_a_data, cols*sizeof(float), dev_a_data, pitch, 
    //                            cols*sizeof(float), rows, cudaMemcpyDeviceToHost) );
    HANDLE_ERROR( cudaMemcpy( host_result, dev_result, rows*sizeof(float), cudaMemcpyDeviceToHost ) );

    //获取结束时间,并显示计时结果
    HANDLE_ERROR( cudaEventRecord( stop, 0 ) );
    HANDLE_ERROR( cudaEventSynchronize( stop ) );
    HANDLE_ERROR( cudaEventElapsedTime( &elapsedTime, start, stop ) );
    printf( "Time to generate:  %3.2f ms\n", elapsedTime );
    HANDLE_ERROR( cudaEventDestroy( start ) );
    HANDLE_ERROR( cudaEventDestroy( stop ) );

    //打印主机端矩阵数据中的计算结果
    for(int i = 0;i < 20;i++){
    
    
        printf("%4.4f ",host_a_data[i]);
        if(i%cols == cols-1)
            printf("\n");
    }
    printf("\n");
    for(int i = 0;i < 32;i++){
    
    
        printf("%f ",host_result[i]);
        if(i%4 == 3)
            printf("\n");
    }

    //释放GPU设备端内存
    HANDLE_ERROR( cudaFree( dev_a ) );
    HANDLE_ERROR( cudaFree( dev_a_data ) );
    //释放CPU主机端内存
    free( host_a );
    free( host_a_data );
}

二维矩阵乘法

  设矩阵都为n阶方阵,则设定 n × n n\times n n×n的线程块网格,每个线程块1024个线程执行矢量点积运算,并执行求和归约,可以设定共享内存以提升内存读取速度.代码示例有.

__global__ void multiply(float **dist, float **x, float **result, int *sumColBoolIndexList, int Num, int m, int p){
    
    
    /*计算过程为q(:,index_Q)'*data(t).x./sum_Q(index_Q)'*/
    /*其中涉及到两个矩阵相乘A*B,设A为Num行m列,B为m行p列*/
    /*其中的Num行,等于非零列总数*/
    /*block线程块网格为x方向Num个,y方向p个*/
    /*每个线程块有1024个线程,负责A一行和B一列m个元素的矢量点积*/
    int tid_x = sumColBoolIndexList[blockIdx.x];
    int tid_y = blockIdx.y;
    __shared__ float cache[1024];
    int cacheIndex = threadIdx.x;
    float temp = 0;
    for(int tid_z = threadIdx.x;tid_z < m;tid_z += blockDim.x){
    
    
        /*负责长度为m的矢量点积*/
        temp += dist[tid_z][tid_x] * x[tid_y][tid_z];
    }
    cache[cacheIndex] = temp;
    __syncthreads();
    int i = 0;
    while(i != 0){
    
    
        if(cacheIndex < i)
            cache[cacheIndex] += cache[cacheIndex + i];
        __syncthreads();
        i /= 2;
    }
    if(cacheIndex == 0)
        result[tid_y][tid_x] = cache[0];
}

猜你喜欢

转载自blog.csdn.net/wanchaochaochao/article/details/91549984
今日推荐