• CUDA 学习记录


    1.关于volatile:

    对于文章中这个函数,

    1. __global__ void reduceUnrollWarps8 (int *g_idata, int *g_odata, unsigned int n)
    2. {
    3. // set thread ID
    4. unsigned int tid = threadIdx.x;
    5. unsigned int idx = blockIdx.x * blockDim.x * 8 + threadIdx.x;
    6. // convert global data pointer to the local pointer of this block
    7. int *idata = g_idata + blockIdx.x * blockDim.x * 8;
    8. // unrolling 8
    9. if (idx + 7 * blockDim.x < n)
    10. {
    11. int a1 = g_idata[idx];
    12. int a2 = g_idata[idx + blockDim.x];
    13. int a3 = g_idata[idx + 2 * blockDim.x];
    14. int a4 = g_idata[idx + 3 * blockDim.x];
    15. int b1 = g_idata[idx + 4 * blockDim.x];
    16. int b2 = g_idata[idx + 5 * blockDim.x];
    17. int b3 = g_idata[idx + 6 * blockDim.x];
    18. int b4 = g_idata[idx + 7 * blockDim.x];
    19. g_idata[idx] = a1 + a2 + a3 + a4 + b1 + b2 + b3 + b4;
    20. }
    21. __syncthreads();
    22. // in-place reduction in global memory
    23. for (int stride = blockDim.x / 2; stride > 32; stride >>= 1)
    24. {
    25. if (tid < stride)
    26. {
    27. idata[tid] += idata[tid + stride];
    28. }
    29. // synchronize within threadblock
    30. __syncthreads();
    31. }
    32. // unrolling warp
    33. if (tid < 32)
    34. {
    35. volatile int *vmem = idata;
    36. vmem[tid] += vmem[tid + 32];
    37. vmem[tid] += vmem[tid + 16];
    38. vmem[tid] += vmem[tid + 8];
    39. vmem[tid] += vmem[tid + 4];
    40. vmem[tid] += vmem[tid + 2];
    41. vmem[tid] += vmem[tid + 1];
    42. }
    43. // write result for this block to global mem
    44. if (tid == 0) g_odata[blockIdx.x] = idata[0];
    45. }

    在  //unrolling wrap 之前的for循环完成后,只剩下idata[0] 到 idata[63](共64个元素)的数据还没有进行加和。

    之前的疑问是 最后的if语句块内为什么会完成这个操作而不出问题:

    因为一个线程束内的线程会进行同步,所以if里的语句是,所有线程执行第一条语句,都执行完了,再执行第二条语句。

    然后关于volitale ,大家都说是禁止编译器优化,我的理解是:

    如果没有volatile,各个线程写寄存器的时候是有同步的,从寄存器写到共享内存是没有同步的。(不过在我这里,去掉volatile结果也是对的。。。)

    2.关于延迟隐藏

    目前个人理解就是让一个线程尽可能多做事。

    3.关于如何获得最大线程束 

    参考:【精选】CUDA编程:笔记2_线程束_longlongqin的博客-CSDN博客

    little法则给出了下面的计算公式"所需线程束 = 延迟 × 吞吐量 

    带宽:一般指的是理论峰值,最大每个时钟周期能执行多少个指令;

    吞吐量:是指实际操作过程中每分钟处理多少个指令。

    每次想到还是看书吧。 

    一个WRAP访存周期是T,刚好是Wrap处理指令时间周期t的K倍,只需要K个wrap就能隐藏访存带来的延迟。

    得到最大线程束数量还要减少分支

    4.选择合适的网格和块:

    有个工具包(同学说不能使了,但是应该也提供了别的方法)。

    4.什么是事务内存

    参考:初识事务内存(Transactional Memory) - 知乎

    个人理解:比u锁更好的具有原子性的互斥手段。 

    5.在GPU上只有内存加载操作可以被缓存,内存存储操作不能被缓存。

    内存加载操作:从全局内存或其他内存读数据加载到寄存器或缓存中。

    内存存储操作:将数据从寄存器或缓存写入到内存

    线程加载数据时,如果缓存中有数据,可以直接从缓存中加载。而存储操作通常不能被缓存,因为要保证数据的一致性,必须及时更新内存中的数据,而不能依赖于缓存。

    6.对于固定内存和零拷贝内存

    当传输大数据时,使用固定内存:cudaError_t cudaMallocHost(void **devPtr, size_t count);

    cudaError_t cudaFreeHost(void *ptr);

    如果想要共享主机和设备端的少量数据,使用零拷贝内存:

    cudaErroer_t cudaHostAlloc(void **pHost, size_t count, unsigned int flags);

    cudaFreeHost()

    使用下列函数获取映射到固定内存的设备指针:

    cudaError_t cudaHostGetDevicePointer(void **pDevice, void *pHost, unsigned int flags);

    1. // part 2: using zerocopy memory for array A and B
    2. // allocate zerocpy memory
    3. CHECK(cudaHostAlloc((void **)&h_A, nBytes, cudaHostAllocMapped));
    4. CHECK(cudaHostAlloc((void **)&h_B, nBytes, cudaHostAllocMapped));
    5. // initialize data at host side
    6. initialData(h_A, nElem);
    7. initialData(h_B, nElem);
    8. memset(hostRef, 0, nBytes);
    9. memset(gpuRef, 0, nBytes);
    10. // pass the pointer to device
    11. CHECK(cudaHostGetDevicePointer((void **)&d_A, (void *)h_A, 0));
    12. CHECK(cudaHostGetDevicePointer((void **)&d_B, (void *)h_B, 0));
    13. // add at host side for result checks
    14. sumArraysOnHost(h_A, h_B, hostRef, nElem);
    15. // execute kernel with zero copy memory
    16. sumArraysZeroCopy<<>>(d_A, d_B, d_C, nElem);
    17. // copy kernel result back to host side
    18. CHECK(cudaMemcpy(gpuRef, d_C, nBytes, cudaMemcpyDeviceToHost));
    19. // check device results
    20. checkResult(hostRef, gpuRef, nElem);
    21. // free memory
    22. CHECK(cudaFree(d_C));
    23. CHECK(cudaFreeHost(h_A));
    24. CHECK(cudaFreeHost(h_B));

    有了虚拟内存寻址(UVA)后,由cudaHostAlloc()分配的固定主机内存具有相同的主机和设备指针,可以将返回的指针直接传递给核函数:

    1. cudaHostAlloc((void **)&h_A, nBytes, cudaHostAllocMapped);
    2. cudaHostAlloc((void **)&h_B, nBytes, cudaHostAllocMapped);
    3. initialData(h_A, nElem);
    4. initialData(h_B, nElem);
    5. sumArrayZeroCopy<<>>(h_A, h_B, d_C, nElem);

  • 相关阅读:
    弘辽科技:100块的直通车,怎么开?电商运营新手篇
    浅谈Spring中JDK动态代理和CGLIB动态代理
    Java内部类(成员内部类、静态内部类、局部内部类、局部内部类)
    扁平数据转树形结构,让数据管理更清晰
    企业经营异常查询API:保障供应链的可靠性与稳定性
    这个好玩又实用的jupyter插件我真的爱了
    Linux服务器(银河麒麟、CentOS 7+、CentOS 7+ 等)修改IP地址
    技术分享 | app自动化测试(Android)--显式等待机制
    【408数据结构与算法】—栈和队列(七)
    【UE 材质】模型部分透明
  • 原文地址:https://blog.csdn.net/qq_52758467/article/details/133941145