并行编程实战——CUDA基础的知识点

一、CUDA并行编程

前面已经对CUDA编程进行了非常详细的分析和说明,理论上讲,应该对CUDA的知识点有了一个整体脉络清晰的了解和学习。但在理解了这些内容后,会有一个问题。在实践中如何使用,如何用自己的语言将这些内容组织出来。特别是在面对面试等特定的场景下,如何能够将它们系统完整的表述清楚,成为了一个检查对CUDA理解程度的一个重要方式。

二、基础的知识点

CUDA的知识点非常多,别说对于初学者,就是对于老鸟,也可能在某些细节上了解不清楚。特别是随着版本的升级尤其是大版本的升级,大家都处于同一个起跑线上。但对于CUDA编程来说,基础的技术点还是要能够在心中有数,做到随问随答,胸有丘壑。

在CUDA的基础技术中,以下几个知识点一定要掌握:

  1. CUDA软硬件知识

    CUDA是支持异构计算的一个框架,不同架构GPU的算力不同会导致CUDA支持版本兼容性的不同。在细节上体现为可能不支持某些技术甚至无法进行编译。特别是需要使用CUDA进行深度学习应用的开发者,更要掌握与cuDNN版本兼容的情况。

  2. 并行模型

    CUDA最基础的知识就是并行模型或者说线程模型,也可以理解为编程模型。在线程模型中,必须掌握其三层架构即Grid、Block和Thread。必须掌握Warp,Warp和线程是什么关系。如果线程中有分支或者说Warp纠缠是什么都要明白其中的道理。

    GPU上执行线程的基本单元是SM,每个GPU根据不同的架构或版本可以有不同多个SM。而每个SM也可以运行多个Block。而线程运行的基本单元是Warp,一般是由32个线程组成,它们以SIMT方式执行同一指令(重点,同一指令)。如果Warp内有分支,则调度时GPU会先执行true分支线程,再执行false分支线程。执行某一分支时,另外一个分支的线程则处于非活动状态即Warp Divergence,这会导致性能的急剧下降。

  3. 内存模型

    其实和学习主机编程一样,CUDA编程一个重点也是内存的问题,如果对主机编程掌握深的话,学习CUDA内存也会很轻松。对CUDA来说,要掌握其内存模型的层次即寄存器、共享内存、全局内存、常量内存和纹理内存。需要注意的是,统一内存并不属于这个内存模型,它只是一种内存处理的机制。

    在这里只要掌握住全局内存容量大(GB)、全局公有访问但速度慢;共享内存处于同一块内,较小(48KB),块内线程共享,访问速度极快(次于寄存器);常量内存容量较小(64KB),全局公有访问速度快(基于缓存);纹理内存较大,全局公有访问,速度快(基于局部性)。而寄存器则最快,但也最小(KB级)且局限于线程内部使用。

    这和CPU中的内存模型没有本质区别。所谓快与慢,其实就是性能和容易的互相妥协。比如共享内存之所以快就是集成于芯片内部,为SM专享,延迟低,速度自然快;反之全局内存则要进行总线通信,延迟高,自然速度慢。

    内存模型中还要注意内存的合并访问以及Bank Conflict。前者是为了提高效率将内存地址合并到连续、对齐且访问宽度一致的内存中。而后者则如同主机CPU多线程访问,如果GPU中多个线程访问同一Bank则可能产生冲突。需要将不同线程访问的数据分散到不同的Bank或进行字节填充增大偏移。

  4. CUDA的流

    如果说CUDA与主机编程的一个显著不同,可能就是流编程。前面提到过,利用流可以实现CPU和GPU的重叠工作,有点类似于重叠IO。通过内存的异步拷贝和固定内存来提高访问的效率

  5. GPU的利用率

    和CPU编程一样,最大限度的压榨GPU的利用率也是编程的一个重要的指标。也就是说,让SM上实际的活动的Warp数量与设计的最大支持量尽量保持一致。在CUDA编程中,可以使用Occupancy这个指标来衡量。

    复制代码
    Occupancy = 实际活跃Warp数 / SM最大支持Warp数

    它其实反应了真正的并行能力。这里会提到涉及到另外一个术语隐藏延迟,足够数量的Warp,可以通过快速切换来抵销内存或指令的即保证计算单元的最大利用率,避免因为数据等而产生闲置的情况。但需要注意的是,Occupancy的值不是越高越好,它受寄存器的数量、共享内存大小以及Block内的线程数影响。

不是说其它的一些知识点不重要,比如图。但图的引入本身就很靠后,而且图的技术点在不断的更新,这也导致可能图的表述可能变化比较大。只是说,基础的知识点往往能够更好的体现对CUDA理解的能力和知识外延的程度

四、知识点的应用

其实上面的几个知识点,在CUDA开发中是非常基础的,它几乎是每个CUDA开发都无法避免的情况。对于Occupancy,通过调整Kernel资源配置,比如线程块的大小、寄存器和共享内存的数量。保证每个SM上的尽可能合适的活跃Warp,就可以极大的提高GPU的利用率。常见的线程块可以选取32的倍数,并使用cudaOccupancyMaxPotentialBlockSize等接口调整和计算最优块的大小。特别是面对内存密集密集型的应用,更是如此。

可以使用线程索引与数组索引保持线性映射的方法或使用共享内存进行数据的合并,提高Coalesced Memory Access的情况,从而加快内存访问的速度。

使用固定内存加速CPU和GPU间内存传输,并且利用异步拷贝和零拷贝等技术来提高数据交互的效率。进而提高整个应用程序的效率。

针对上面的情况再辅以NVIDIA提供的相关工具如Nsight等,就可以在深度学习、音视频处理等实际的应用场景中优化相关的代码,提高处理的速度。

五、总结

侯捷老师说过,勿在浮砂筑高台,其实就是万丈高楼平地起的意思。把CUDA编程的技术点掌握的通透,能够用自己的语言详尽的分析和说明,就说明已经真正的摸到了CUDA编程的门槛,剩下的只是个人的努力的实际应用的结合了。

相关推荐
代钦塔拉4 小时前
C++ auto
开发语言·c++
我命由我123455 小时前
C++ - 面向对象 - 常成员函数
android·java·linux·c语言·开发语言·c++·算法
徐安安_ye15 小时前
FlashAttention与文本生成:让AI妙笔生花
c++·人工智能·mfc
ThornArmor5 小时前
【控制篇】斩断无休止空转:4-bit 指令集里的跳转律令与时序状态机
c语言·汇编·c++·单片机·嵌入式硬件
星轨初途8 小时前
【C++进阶】vector 类从入门到精通:核心接口与内存机制实战指南
c语言·开发语言·c++·经验分享·笔记·柔性数组
kyle~8 小时前
GigE Vision---GVCP( GigE Vision Control Protocol,GV控制协议)
linux·c++·机器人·工业相机·传感器
cjhbachelor8 小时前
C++知识点
开发语言·c++
kyle~8 小时前
相机驱动---零拷贝mmap映射
linux·运维·c++·机器人
郝学胜-神的一滴8 小时前
Qt 高级开发 015:C++ 原生实现信号槽机制
开发语言·c++·qt·软件构建·用户界面