CUDA:大型ish 2D阵列上的共享内存
我有一个简单的CUDA问题,但教授增加了一个可选任务,用共享内存实现相同的算法。我无法在截止日期前完成它(例如,交工日期是一周前),但我仍然很好奇,所以现在我要问一下互联网;) 基本任务是在CUDA和CUDA中实现红黑连续超松弛的bastardized版本,确保两者的结果相同,然后比较加速比。就像我说的,使用共享内存是可选的+10%附加组件 我将发布我的工作版本和伪代码,因为我现在手头上没有代码,所以我尝试做些什么,但如果有人需要,我可以稍后用实际代码更新 在任何人说之前:是的,我知道使用CUtil是站不住脚的,但它使比较和计时器更容易 工作全局内存版本:CUDA:大型ish 2D阵列上的共享内存,cuda,Cuda,我有一个简单的CUDA问题,但教授增加了一个可选任务,用共享内存实现相同的算法。我无法在截止日期前完成它(例如,交工日期是一周前),但我仍然很好奇,所以现在我要问一下互联网;) 基本任务是在CUDA和CUDA中实现红黑连续超松弛的bastardized版本,确保两者的结果相同,然后比较加速比。就像我说的,使用共享内存是可选的+10%附加组件 我将发布我的工作版本和伪代码,因为我现在手头上没有代码,所以我尝试做些什么,但如果有人需要,我可以稍后用实际代码更新 在任何人说之前:是的,我知道使用CUt
#include <stdlib.h>
#include <stdio.h>
#include <cutil_inline.h>
#define N 1024
__global__ void kernel(int *d_A, int *d_B) {
unsigned int index_x = blockIdx.x * blockDim.x + threadIdx.x;
unsigned int index_y = blockIdx.y * blockDim.y + threadIdx.y;
// map the two 2D indices to a single linear, 1D index
unsigned int grid_width = gridDim.x * blockDim.x;
unsigned int index = index_y * grid_width + index_x;
// check for boundaries and write out the result
if((index_x > 0) && (index_y > 0) && (index_x < N-1) && (index_y < N-1))
d_B[index] = (d_A[index-1]+d_A[index+1]+d_A[index+N]+d_A[index-N])/4;
}
main (int argc, char **argv) {
int A[N][N], B[N][N];
int *d_A, *d_B; // These are the copies of A and B on the GPU
int *h_B; // This is a host copy of the output of B from the GPU
int i, j;
int num_bytes = N * N * sizeof(int);
// Input is randomly generated
for(i=0;i<N;i++) {
for(j=0;j<N;j++) {
A[i][j] = rand()/1795831;
//printf("%d\n",A[i][j]);
}
}
cudaEvent_t start_event0, stop_event0;
float elapsed_time0;
CUDA_SAFE_CALL( cudaEventCreate(&start_event0) );
CUDA_SAFE_CALL( cudaEventCreate(&stop_event0) );
cudaEventRecord(start_event0, 0);
// sequential implementation of main computation
for(i=1;i<N-1;i++) {
for(j=1;j<N-1;j++) {
B[i][j] = (A[i-1][j]+A[i+1][j]+A[i][j-1]+A[i][j+1])/4;
}
}
cudaEventRecord(stop_event0, 0);
cudaEventSynchronize(stop_event0);
CUDA_SAFE_CALL( cudaEventElapsedTime(&elapsed_time0,start_event0, stop_event0) );
h_B = (int *)malloc(num_bytes);
memset(h_B, 0, num_bytes);
//ALLOCATE MEMORY FOR GPU COPIES OF A AND B
cudaMalloc((void**)&d_A, num_bytes);
cudaMalloc((void**)&d_B, num_bytes);
cudaMemset(d_A, 0, num_bytes);
cudaMemset(d_B, 0, num_bytes);
//COPY A TO GPU
cudaMemcpy(d_A, A, num_bytes, cudaMemcpyHostToDevice);
// create CUDA event handles for timing purposes
cudaEvent_t start_event, stop_event;
float elapsed_time;
CUDA_SAFE_CALL( cudaEventCreate(&start_event) );
CUDA_SAFE_CALL( cudaEventCreate(&stop_event) );
cudaEventRecord(start_event, 0);
// TODO: CREATE BLOCKS AND THREADS AND INVOKE GPU KERNEL
dim3 block_size(256,1,1); //values experimentally determined to be fastest
dim3 grid_size;
grid_size.x = N / block_size.x;
grid_size.y = N / block_size.y;
kernel<<<grid_size,block_size>>>(d_A,d_B);
cudaEventRecord(stop_event, 0);
cudaEventSynchronize(stop_event);
CUDA_SAFE_CALL( cudaEventElapsedTime(&elapsed_time,start_event, stop_event) );
//COPY B BACK FROM GPU
cudaMemcpy(h_B, d_B, num_bytes, cudaMemcpyDeviceToHost);
// Verify result is correct
CUTBoolean res = cutComparei( (int *)B, (int *)h_B, N*N);
printf("Test %s\n",(1 == res)?"Passed":"Failed");
printf("Elapsed Time for Sequential: \t%.2f ms\n", elapsed_time0);
printf("Elapsed Time for CUDA:\t%.2f ms\n", elapsed_time);
printf("CUDA Speedup:\t%.2fx\n",(elapsed_time0/elapsed_time));
cudaFree(d_A);
cudaFree(d_B);
free(h_B);
cutilDeviceReset();
}
#包括
#包括
#包括
#定义n1024
__全局无效内核(int*d\u A,int*d\u B){
无符号整数索引_x=blockIdx.x*blockDim.x+threadIdx.x;
无符号整数索引_y=blockIdx.y*blockDim.y+threadIdx.y;
//将两个二维索引映射到单个线性一维索引
无符号整数网格宽度=gridDim.x*blockDim.x;
无符号整数索引=索引y*网格宽度+索引x;
//检查边界并写出结果
如果((指数x>0)和&(指数y>0)和&(指数x 对于(i=0;i在CUDA中最大限度地利用这些模具操作符的关键是数据的重复使用。我发现最好的方法通常是让每个块“遍历”网格的一个维度。在块将初始数据块加载到共享内存中后,只有一个维度(因此,行中的行主要顺序为2D问题)需要从全局内存中读取数据,以便在共享内存中存储第二行和后续行计算所需的数据。其余数据可以重复使用。要通过此类算法的前四个步骤直观显示共享内存缓冲区的外观,请执行以下操作:
输入网格的三个“行”(a、b、c)被加载到共享内存中,模具为行(b)计算并写入全局内存
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbb
中交
将另一行(d)加载到共享内存缓冲区中,替换行(a),并使用不同的模具对行(c)进行计算,以反映行数据在共享内存中的位置
dddddddddddddddddddd
bbbbbbbbbbbbbbbbbbbb
中交
将另一行(e)加载到共享内存缓冲区中,替换行(b)和对行(d)进行的计算,使用与步骤1或2不同的模具
dddddddddddddddddddd
依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依
中交
另一行(f)加载到共享内存缓冲区中,替换行(c)和对行(e)进行的计算。现在数据返回到步骤1中使用的相同布局,并且可以使用步骤1中使用的相同模具
dddddddddddddddddddd
依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依依
FFFFFFFFFFFFFF
整个循环将重复,直到块穿过输入网格的整列长度。使用不同的模板而不是移动共享内存缓冲区中的数据的原因在于性能-共享内存在费米上只有约1000 Gb/s的带宽,数据的移动将成为完全优化代码中的瓶颈。您应该尝试不同的缓冲区大小,因为您可能会发现更小的缓冲区允许更高的占用率和改进的内核吞吐量
编辑:给出一个具体的示例,说明如何实现:
template<int width>
__device__ void rowfetch(int *in, int *out, int col)
{
*out = *in;
if (col == 1) *(out-1) = *(in-1);
if (col == width) *(out+1) = *(in+1);
}
template<int width>
__global__ operator(int *in, int *out, int nrows, unsigned int lda)
{
// shared buffer holds three rows x (width+2) cols(threads)
__shared__ volatile int buffer [3][2+width];
int colid = threadIdx.x + blockIdx.x * blockDim.x;
int tid = threadIdx.x + 1;
int * rowpos = &in[colid], * outpos = &out[colid];
// load the first three rows (compiler will unroll loop)
for(int i=0; i<3; i++, rowpos+=lda) {
rowfetch<width>(rowpos, &buffer[i][tid], tid);
}
__syncthreads(); // shared memory loaded and all threads ready
int brow = 0; // brow is the next buffer row to load data onto
for(int i=0; i<nrows; i++, rowpos+=lda, outpos+=lda) {
// Do stencil calculations - use the value of brow to determine which
// stencil to use
result = ();
// write result to outpos
*outpos = result;
// Fetch another row
__syncthreads(); // Wait until all threads are done calculating
rowfetch<width>(rowpos, &buffer[brow][tid], tid);
brow = (brow < 2) ? (brow+1) : 0; // Increment or roll brow over
__syncthreads(); // Wait until all threads have updated the buffer
}
}
模板
__设备\无效行提取(int*in、int*out、int-col)
{
*out=*in;
如果(col==1)*(out-1)=*(in-1);
如果(col==宽度)*(out+1)=*(in+1);
}
模板
__全局_uu运算符(int*in、int*out、int-nrows、无符号int-lda)
{
//共享缓冲区容纳三行x(宽度+2)列(线程)
__共享_uuuuuuuuvolatile int缓冲区[3][2+宽度];
int colid=threadIdx.x+blockIdx.x*blockDim.x;
inttid=threadIdx.x+1;
int*rowpos=&in[colid],*outpos=&out[colid];
//加载前三行(编译器将展开循环)
对于(int i=0;我没有这样想,谢谢。问题是,我如何防止块中的线程相互交叉?假设我在一个块中有两个线程,线程2希望加载行(f),而线程1仍在处理行(c)?还是我应该将代码更改为每个块有一个线程,然后有几个块?@a5ehren:有一个块内同步原语,名为u syncthreads()您可以使用它来同步线程。理想情况下,您需要每个块32个线程的整数倍,以及覆盖输入空间行宽度所需的尽可能多的块。如果您需要更多帮助,我可以在答案中添加一点伪代码。因此,我会让每个线程加载行的一部分,进行同步,然后假设有线程处理上面和下面的行?我想一些伪代码会有帮助:PI在问题帖子的末尾添加了一些代码…你能看一下,看看我是否在正确的轨道上吗?这是一个非常有用的答案。在一个指针赋值中有一个小错误,一个括号放错了位置,所以我编辑了它。在我离开后的三周内在开始学习CUDA时,我遇到了很多你的答案,这些答案非常有帮助。你对酷玩的看法也非常正确。
template<int width>
__device__ void rowfetch(int *in, int *out, int col)
{
*out = *in;
if (col == 1) *(out-1) = *(in-1);
if (col == width) *(out+1) = *(in+1);
}
template<int width>
__global__ operator(int *in, int *out, int nrows, unsigned int lda)
{
// shared buffer holds three rows x (width+2) cols(threads)
__shared__ volatile int buffer [3][2+width];
int colid = threadIdx.x + blockIdx.x * blockDim.x;
int tid = threadIdx.x + 1;
int * rowpos = &in[colid], * outpos = &out[colid];
// load the first three rows (compiler will unroll loop)
for(int i=0; i<3; i++, rowpos+=lda) {
rowfetch<width>(rowpos, &buffer[i][tid], tid);
}
__syncthreads(); // shared memory loaded and all threads ready
int brow = 0; // brow is the next buffer row to load data onto
for(int i=0; i<nrows; i++, rowpos+=lda, outpos+=lda) {
// Do stencil calculations - use the value of brow to determine which
// stencil to use
result = ();
// write result to outpos
*outpos = result;
// Fetch another row
__syncthreads(); // Wait until all threads are done calculating
rowfetch<width>(rowpos, &buffer[brow][tid], tid);
brow = (brow < 2) ? (brow+1) : 0; // Increment or roll brow over
__syncthreads(); // Wait until all threads have updated the buffer
}
}