9. CUDA shared memory使用------GPU的革命

37 篇文章 0 订阅

9. CUDA shared memory使用------GPU的革命

序言:明年就毕业了,下半年就要为以后的生活做打算。这半年,或许就是一个抉择的时候,又是到了一个要做选择的时候。或许是自己的危机意识比较强,一直都觉得自己做得不够好,还需要积累和学习。或许是知足常乐吧,从小山沟,能到香港,一步一步,自己都比较满足,只是心中一直抱着一个理想,坚持做一件事情,坚持想做点事情,踏踏实实,曾经失败过,曾经迷茫过,才学会了坚持,学会了坚毅,才体会了淡定和从容。人生路上,一路走,一路看,一路学,抱着感恩的心,帮助别人,就是帮助自己,未来的路才会更宽……

正文:书接上文《8. CUDA 内存使用 global 二------GPU的革命》 讲了global内存访问的时候,需要对齐的问题,只有在对齐的情况下才能保证global内存的高效访问。这一章节准备写一下shared memory的访问的问题,首先是讲一下shared的memory的两种使用方法,然后讲解一下shared memory的bank conflict的问题,这个是shared memory访问能否高效的问题所在;

Shared memory的常规使用:

1. 使用固定大小的数组:

/************************************************************************/

/* Example */

/************************************************************************/

__global__ void shared_memory_1(float* result, int num, float* table_1)

{

__shared__ float sh_data[THREAD_SIZE];

int idx = threadIdx.x;

float ret = 0.0f;

sh_data[idx] = table_1[idx];

for (int i = 0; i < num; i++)

{

ret += sh_data[idx %BANK_CONFLICT];

}

result[idx] = ret;

}

这里的sh_data就是固定大小的数组;

2. 使用动态分配的数组:

extern __shared__ char array[];

__global__ void shared_memory_1(float* result, int num, float* table_1, int shared_size)

{

float* sh_data = (float*)array; // 这里就让sh_data指向了shared memory的第一个地址,就可以动态分配空间

float* sh_data2 = (float*)&sh_data[shared_size]; // 这里的shared_size的大小为sh_data的大小;

int idx = threadIdx.x;

float ret = 0.0f;

sh_data[idx] = table_1[idx];

for (int i = 0; i < num; i++)

{

ret += sh_data[idx %BANK_CONFLICT];

}

result[idx] = ret;

}

这里是动态分配的空间,extern __shared__ char array[];指定了shared的第一个变量的地址,这里其实是指向shared memory空间地址;后面的动态分配float* sh_data = (float*)array;让sh_data指向array其实就是指向shared memory上的第一个地址;

后面的float* sh_data2 = (float*)&sh_data[shared_size];这里的sh_data2是指向的第一个sh_data的shared_size的地址,就是sh_data就是有了shared_size的动态分配的空间;

入下图:

clip_image002[8]

3. 下面是讲解bank conflict

我们知道有每一个half-warp是16个thread,然后shared memory有16个bank,怎么分配这16个thread,分别到各自的bank去取shared memory,如果大家都到同一个bank取款,就会排队,这就造成了bank conflict,上面的代码可以用来验证一下bank conflict对代码性能造成的影响:

/************************************************************************/

/* Example */

/************************************************************************/

__global__ void shared_memory_1(float* result, int num, float* table_1)

{

__shared__ float sh_data[THREAD_SIZE];

int idx = threadIdx.x;

float ret = 0.0f;

sh_data[idx] = table_1[idx];

for (int i = 0; i < num; i++)

{

ret += sh_data[idx %BANK_CONFLICT];

}

result[idx] = ret;

}

// 1,2,3,4,5,6,7.....16

#define BANK_CONFLICT 16

这里的BANK_CONFLICT 定义为从1到16的大小,可以自己修改,来看看bank conflict对性能的影响;当BANK_CONFLICT为2的时候,就会通用有8个thread同时访问同一个bank,因为idx%2的取值只有2个0和1,所以16个都会访问bank0和bank1,以此类推,就可以测试整个的性能;

下面为示意图:

clip_image004[9]

当然我们还可以利用16bank conflict,大家都访问同一个bank的同一个数据的时候,就可以形成一个broadcast,那样就会把数据同时广播给16个thread,这样就可以合理利用shared memory的broadcast的机会。

下面贴出代码,最好自己测试一下;

/********************************************************************

* shared_memory_test.cu

* This is a example of the CUDA program.

* Author: zhao.kaiyong(at)gmail.com

* http://blog.csdn.net/openhero

* http://www.comp.hkbu.edu.hk/~kyzhao/

*********************************************************************/

#include

#include

#include

#include

// 1,2,3,4,5,6,7.....16

#define BANK_CONFLICT 16

#define THREAD_SIZE 16

/************************************************************************/

/* static */

/************************************************************************/

__global__ void shared_memory_static(float* result, int num, float* table_1)

{

__shared__ float sh_data[THREAD_SIZE];

int idx = threadIdx.x;

float ret = 0.0f;

sh_data[idx] = table_1[idx];

for (int i = 0; i < num; i++)

{

ret += sh_data[idx%BANK_CONFLICT];

}

result[idx] = ret;

}

/************************************************************************/

/* dynamic */

/************************************************************************/

extern __shared__ char array[];

__global__ void shared_memory_dynamic(float* result, int num, float* table_1, int shared_size)

{

float* sh_data = (float*)array; // 这里就让sh_data指向了shared memory的第一个地址,就可以动态分配空间

float* sh_data2 = (float*)&sh_data[shared_size]; // 这里的shared_size的大小为sh_data的大小;

int idx = threadIdx.x;

float ret = 0.0f;

sh_data[idx] = table_1[idx];

for (int i = 0; i < num; i++)

{

ret += sh_data[idx%BANK_CONFLICT];

}

result[idx] = ret;

}

/************************************************************************/

/* Bank conflict */

/************************************************************************/

__global__ void shared_memory_bankconflict(float* result, int num, float* table_1)

{

__shared__ float sh_data[THREAD_SIZE];

int idx = threadIdx.x;

float ret = 0.0f;

sh_data[idx] = table_1[idx];

for (int i = 0; i < num; i++)

{

ret += sh_data[idx % BANK_CONFLICT];

}

result[idx] = ret;

}

/************************************************************************/

/* HelloCUDA */

/************************************************************************/

int main(int argc, char* argv[])

{

if ( cutCheckCmdLineFlag(argc, (const char**) argv, "device"))

{

cutilDeviceInit(argc, argv);

}else

{

int id = cutGetMaxGflopsDeviceId();

cudaSetDevice(id);

}

float *device_result = NULL;

float host_result[THREAD_SIZE] ={0};

CUDA_SAFE_CALL( cudaMalloc((void**) &device_result, sizeof(float) * THREAD_SIZE));

float *device_table_1 = NULL;

float host_table1[THREAD_SIZE] = {0};

for (int i = 0; i < THREAD_SIZE; i++ )

{

host_table1[i] = rand()%RAND_MAX;

}

CUDA_SAFE_CALL( cudaMalloc((void**) &device_table_1, sizeof(float) * THREAD_SIZE));

CUDA_SAFE_CALL( cudaMemcpy(device_table_1, host_table1, sizeof(float) * THREAD_SIZE, cudaMemcpyHostToDevice));

unsigned int timer = 0;

CUT_SAFE_CALL( cutCreateTimer( &timer));

CUT_SAFE_CALL( cutStartTimer( timer));

shared_memory_static<<<1, THREAD_SIZE>>>(device_result, 1000, device_table_1);

//shared_memory_dynamic<<<1, THREAD_SIZE>>>(device_result, 1000, device_table_1, 16);

//shared_memory_bankconflict<<<1, THREAD_SIZE>>>(device_result, 1000, device_table_1);

CUT_CHECK_ERROR("Kernel execution failed/n");

CUDA_SAFE_CALL( cudaMemcpy(host_result, device_result, sizeof(float) * THREAD_SIZE, cudaMemcpyDeviceToHost));

CUT_SAFE_CALL( cutStopTimer( timer));

printf("Processing time: %f (ms)/n", cutGetTimerValue( timer));

CUT_SAFE_CALL( cutDeleteTimer( timer));

for (int i = 0; i < THREAD_SIZE; i++)

{

printf("%f ", host_result[i]);

}

CUDA_SAFE_CALL( cudaFree(device_result));

CUDA_SAFE_CALL( cudaFree(device_table_1));

cutilExit(argc, argv);

}

这里只是一个简单的demo,大家可以测试一下。下一章节会将一些shared memory的更多的特性,更深入的讲解shared memory的一些隐藏的性质;

再在接下来的章节会讲一些constant和texture的使用;

写的内容一直都是文字比较多,代码比较少,其实学习的过程更重要的思想,实践的代码,最好是自己写,唯一可以学习的是思想,学习更重要的也是思想的交流,知识的传播,最好的是思想的传播,代码,方法,都是只是一些工具而已。但是工具的熟练层度,就得靠自己下来多练习。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 18
    评论
### 回答1: CUDA共享内存是一种特殊的内存类型,它可以在同一个线程块内的线程之间共享数据。这种内存类型的访问速度非常快,因为它是在GPU芯片上的SRAM中实现的。使用共享内存可以有效地减少全局内存的访问,从而提高CUDA程序的性能。共享内存的大小是有限制的,通常为每个线程块的总共享内存大小的一半。因此,在使用共享内存时需要仔细考虑内存的使用情况,以避免内存溢出和性能下降。 ### 回答2: CUDA shared memory是一种专门用于加速GPU并行计算的高速缓存区域。它位于GPU的多个处理核心之间共享,并在同一个线程块中的线程之间交流数据。相比于全局内存,shared memory具有更低的访问延迟和更高的带宽。 shared memory可以通过声明__shared__关键字来定义,并通过静态分配的方式进行初始化。每个线程块都具有自己独立的shared memory空间,其大小在编译时确定,但最大限制为48KB。 shared memory的主要优点是其高带宽和低延迟。由于其位于多个处理核心之间共享,可以实现线程之间的快速数据交换。通过将计算中频繁使用的数据存储在shared memory中,可以减少从全局内存中读取数据所需的时间。这对于那些具有访存限制的算法,如矩阵乘法和图像处理等,非常有用。 使用shared memory还可以避免线程间的数据冗余读取,从而提高整体的并行计算效率。当多个线程需要访问相同的数据时,可以将这些数据存储在shared memory中,以便线程之间进行共享,从而减少了重复的全局内存访问。 但shared memory也有一些限制和需要注意的地方。首先,shared memory的大小是有限的,需要根据具体的算法和硬件限制进行适当调整。其次,由于其共享的特性,需要确保线程之间的数据同步。最后,使用shared memory时需要注意避免bank conflict,即多个线程同时访问同一个shared memory bank造成的资源竞争,从而导致性能下降。 综上所述,CUDA shared memoryGPU并行计算中具有重要的作用。通过使用shared memory,可以有效减少全局内存访问、提高数据交换速度和并行计算效率,从而加速GPU上的并行计算任务。 ### 回答3: CUDA共享内存(shared memory)是指在CUDA程序中使用的一种特殊的内存空间。它是GPU上的一块高速、低延迟的内存,被用来在同一个线程块(thread block)中的线程之间进行数据共享。 与全局内存相比,共享内存的访问速度更快,读写延迟更低。这是因为共享内存位于SM(Streaming Multiprocessor)内部,可以直接被SM访问,而全局内存则需要通过PCIe总线与主机内存进行通信。 使用共享内存可以提高应用程序性能的原因之一是避免了全局内存的频繁访问。当多个线程需要读写同一个数据时,如果每个线程都从全局内存中读取/写入,会导致内存带宽饱和,限制了整体性能。而将这些数据缓存在共享内存中,可以减少对全局内存的访问次数,提高内存带宽的利用率。 除此之外,共享内存的另一个重要特性是可以用作线程间的通信机制。在同一个线程块中的线程可以通过共享内存交换数据,而无需利用全局内存作为中介。这使得线程之间的协作变得更加高效和灵活。 然而,共享内存也有一些限制。首先,共享内存的大小是有限的,通常为每个SM的一定容量(如16KB或48KB)。其次,共享内存的生命周期与线程块相同,每个线程块结束后,共享内存中的数据将被销毁。 在编写CUDA程序时,可以使用__shared__关键字来声明共享内存。同时需要注意,合理地使用共享内存,并避免冲突和竞争条件,才能充分发挥共享内存的优势,提高CUDA程序的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值