二维矩阵索引
之前的程序只涉及到了一维数组的操作,那么对于二维矩阵如何操作呢?假设有一个 M M M行 N N N列的二维矩阵 A A A,首先在C++中为了能够使用二维索引,需要定义长度为 M ∗ N M*N M∗N的一级数据指针 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];
}