CUDA, 软件抽象的幻影背后

本文原载于我们的博客planckscale.info,转载于此。
版权声明:原创作品,欢迎转载,但转载请以超链接形式注明文章来源(planckscale.info)、作者信息和本声明,否则将追究法律责任。

今天最酷炫的事情应该就是来自老黄的这条消息:1TFLOPS,P < 15W, ARM Cortex A57 * 4 + ARM Cortex A53 * 4 +  Maxwell 256 CUDA Cores,  Tegra X1.

图1.  Tegra X1

本想挖掘一下写篇博,但目前报道满天飞没太大必要了。于是又想起了这个命途多舛的话题:CUDA. 关于CUDA我写了两次,第一次不满意未发,第二次成文后保存失败灰飞烟灭在热力学第二定律决定的命运里。今天借X1的东风,我们再来聊聊CUDA.

**********************************************************

CUDA是个以性能为第一目标的语言,这也决定了CUDA开发者所要面对的复杂性远远要多于CUDA语言所抽象出来的编程模型本身。这大概会是软件抽象所要面对的永恒话题,我们可以去抽象出一组逻辑上漂亮完备的功能基元,却不能保证从性能的观点看它们同样也是小开销的基本操作。具体在CUDA里,最典型的例子是内存<->显存数据交换,一个简单的拷贝操作在性能上却是让人难以接受的,这背后是PCIE总线;对性能影响稍小些的例子比如Global Memory的读写需要考虑对齐,这是由于硬件层面warp和cache机制的体现;再者如过度臃肿的kernel或block过大导致寄存器耗尽,局域变量被吐到Local
Memory导致的性能损失。

所有这些,都要求我们透过CUDA简洁干净的编程模型,看到软件抽象的美丽幻影背后那个不同的世界,它存在于抽象之下我们不熟悉的另一个层次,却透过性能这一个几乎是唯一的方式来影响着我们的软件。这颇类似万有引力与我们世界的关系:引力是唯一能透入额外维度的基本相互作用,如果世界有我们所不知道的维度存在,如何才能感受到那个世界对我们的影响?答案就是用引力。看过《星际穿越》的同学们想必对此有些印象。

在深入GPU的硬件架构之前,我们不妨先探讨一下这个问题:为什么GPU具有这么高的计算能力?我们试着归纳两条最主要的原因。

目前典型的计算模式有两种,CPU式的高速低延迟串行计算,和GPU式的高延迟高吞吐大规模并行计算。CPU是人们熟知的,它具有高速的内部寄存器和Cache,现代CPU又加入了多级流水线,猜测、乱序执行,超线程等技术加速其指令吞吐能力,具有快速的响应能力,但是对于大量数据的处理却相对不够用。这一点3D游戏应用就是典型的例子,当然,这就是GPU崛起的契机。

GPU天生为数据的批量处理而生,它擅长的是在大量数据上同时做同样或几乎一致(这点很重要)的计算。为什么要求一样的计算?这一点可以从很多个角度来回答。

最重要的一个回答是,多个线程同步执行一致的运算,使得我们可以用单路指令流对多个执行单元进行控制,大幅度减少了控制器的个数和系统的复杂度(设想成千上万的线程各自做不同的事情,如果再有线程间通讯/同步,将会是怎样的梦魇)。

另一方面,现实世界中应用在大规模数据上的计算,通常都涵盖在这一计算模式之中,因而考虑更复杂的模式本质上是不必要的。比如计算大气的流动,每一点的风速仅仅取决于该点邻域上的密度和压强分布;再如计算图像的卷积,每一个输出像素都仅是对应源点邻域和一个卷积核的内积。从这些例子中我们可以看到,除了各个数据单元上进行的计算是一样的,计算中数据之间的相互影响也具有某种“局域性”,一个数据单元上的计算最多需要它某个邻域上的数据。这一点意味着线程之间是弱耦合的,邻近线程之间会有一些共享数据(或者是计算结果),远距离的线程间则独立无关。

这个性质反映在CUDA里,就是Block划分的两重天地:Block内部具有Shared Memory,线程间可以共享数据、通讯和同步,Block外部则完全独立,Block间没有通讯机制,相互执行顺序不影响计算结果。这一划分使得我们既可以利用线程间通讯做一些复杂的应用和算法加速,又可以在Block的粒度上自由调度计算任务,在不同计算能力的硬件平台上自适应的调整任务安排。

现在我们把注意力放在“几乎一致”这里。最简单的并行计算方案是多路数据上同时进行完全一致的计算,即SIMD(单指令流多数据流)。这种方案是非常受限的。事实上我们可以看出,“完全一致”是不必要的。只要这些计算在大多数时候完全一致,就可以对它们做SIMD加速,而在计算分叉,各个线程不一致的特殊情况下,只需要分支内并行,分支间串行执行即可,毕竟这些只是很少出现的情况。这样,把“完全一致”这个限制稍微放松,就可以得到更广阔的应用范围和不输于SIMD的计算性能,即SIMT(单指令流多线程)的一个重要环节,这是GPU强大处理能力的第一个原因。

一个或许让每个初学者都惊讶的事实是这样一组数据:Global Memory访存延迟可以达到数百个时钟周期,即便是最快的Shared Memory和寄存器在有写后读依赖时也需要数十个时钟周期。这似乎和CUDA强大的处理能力完全相悖——如果连寄存器都这么慢,怎么会有高性能呢?难道这不会成为最大的瓶颈吗?

答案恰恰就出乎意料:不,这不是瓶颈,这个高延迟的开销被掩盖了,掩盖在大量线程之下。更清楚的说,当一组线程(同步执行,类似于SIMD的一个线程组,在CUDA里叫做warp)因为访存或其他原因出现等待时,就将其挂起,转而执行另一组线程,GPU的硬件体系允许同时有大量线程存活于GPU的SM(流多处理器)之中,控制单元在多组线程之间快速切换,从而保证资源的最大利用率——控制单元始终有指令可以发放,执行单元始终有任务可以执行,仍然可以保持最高的指令吞吐,每个单元基本都能保持充分的忙碌。

这就是GPU硬件设计中非常有特色的基本思想:用多线程掩盖延迟。这一设计区别于CPU的特点是,大量高延迟寄存器取代了少量低延迟寄存器,寄存器的数量保证了可以有大量线程同时存活,且可以在各组线程间快速切换。尽管每个线程是慢的,但庞大的线程数成就了GPU的数据吞吐能力。此为高性能的第二个原因。

这文又要写成未完待续了。接下来的日子,不填完旧坑不再开新话题。

时间: 2024-08-06 16:03:30

CUDA, 软件抽象的幻影背后的相关文章

CUDA, 软件抽象的幻影背后 之二

本文原载于我的主页:planckscale.info,转载于此. 版权声明:原创作品,欢迎转载,但转载请以超链接形式注明文章来源(planckscale.info).作者信息和本声明,否则将追究法律责任. 上一篇里说到,有两点对CUDA的计算能力影响甚大:数据并行,以及用多线程掩盖延迟.接下来我们要深入到其硬件实现,看一看这些机制是如何运作的. 通常人们经常说某GPU有几百甚至数千的CUDA核心,这很容易让人联想到多核CPU.不过事实上两种"核心"是不一样的概念,GPU的CUDA核心只

CUDA, 软件抽象的幻影背后 之三

本文原载于我的主页:planckscale.info,转载于此. 版权声明:原创作品,欢迎转载,但转载请以超链接形式注明文章来源(planckscale.info).作者信息和本声明,否则将追究法律责任. 上一篇中谈到了编程模型中的Block等概念如何映射到硬件上执行,以及CUDA如何用并行来掩盖延迟.这一篇继续剖析SIMT,谈一谈控制流分叉,指令吞吐和线程间通讯机制. 虽然我们说warp中的线程类似于SIMD,但事实上它是真正的线程.warp中的每一个thread都有自己的指令地址寄存器,允许

Socket是什么呢?中间软件抽象层

代表着网络连接 Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议.你会使用它们吗?       前人已经给我们做了好多的事了,网络间的通信也就简单了许多,但毕竟还是有挺多工作要做的.以前听到Socket编程,觉得它是比较高深的编程知识,但是只要弄清Socket编程的工作原理,神秘的面纱也就

软件和硬件都是对生活的高度抽象---论中断控制(ARM体系编程)

不同的芯片体系设计在集成电路系统设计阶段其实都遵循大体一致的设计思想,芯片设计发展那么多年,真正为人所熟知的就是X86架构和ARM架构,当然还有日渐没落的MIPS,其他都是一些简单的控制器芯片体系.而硬件模块设计又是高度抽象于现实需求,很多时候,X86.ARM和MIPS只有底层寄存器和指令级别的差异,对于软件驱动基本是一致的.本文论及ARM体系的中断控制,以基于Cortex A8的S5PV210为例.中断是一种异步工作机制,也是嵌入式处理器的一个核心工作机制,对于实时操作系统来说必不可少. 1.

复杂网络,抽象语法树

近期看了一些软件抽象为复杂网络,以及软件抽象成静态语法树的文章.做一个小总结. 1.复杂网络是由大量的边和点组成的,边点都可以有类型,加权值,边还可以有方向.如何计算边和点的权值是一个关键点,如何在不执行代码的情况下确定边的方向,目前不确定是否已经解决. 有许多工具,可以直接扫描软件源代码,抽象为复杂网络.然而我还没亲身实践,且做个记录. Dependency Finder分析编译后的java代码,能够提取依赖图. Doxygen是使用c++开发的基于源代码注释的文档生成工具.但是这个注释,是人

CUDA学习和总结1

一. 基本概念 1. CUDA 2007年,NVIDIA推出CUDA(Compute Unified Device Architecture,统一计算设备架构)这个编程模型,目的是为了在应用程序中充分利用CPU和GPU各自的优点,实现CPU/GPU联合执行.这种联合执行的需要已经在最新的集中编程模型(OpenCL,OpenACC,C++ AMP)中体现出来了. 2. 并行编程语言和模型 使用比较广泛的是为可扩展的集群计算设计的消息传递接口(Message Passing Interface,MP

回写、套接字socket 、JVM映像、实际/抽象回话

1.什么是回写? 回写:更新多维数据集单元值.成员或成员属性值. 操作系统和平台上的应用程序在运行的时候需要往磁盘写入临时数据,但是在无盘环境下,没有硬盘作为操作系统和应用程序临时的交换数据空间,所以这个任务必须交给服务器来完成 计算机回写:“Write Back(回写),在回写状态下,数据只有在要被从高速缓存中清除时才写到磁盘上.随着主存读取的数据增加,回写需要开始从高速缓存中向磁盘上写数据,并把更新的数据写入高速缓存中.由于一个数据可能会被写入高速缓存中许多次,而没有进行磁盘存取,所以回写的

Android网络编程系列 一 Socket抽象层

在<Android网络编程>系列文章中,前面已经将Java的通信底层大致的描述了,在我们了解了TCP/IP通信族架构及其原理,接下来我们就开始来了解基于tcp/ip协议层的Socket抽象层.本篇文章将会让我们清楚的了解和学会使用Socket. 什么是Socket?它又是如何运作的?它的表现形式是什么?等等这些问题,本篇文章将逐步的揭晓. 一. 什么是Socket Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,你可以借助它建立TCP连接等等,而http是个应用层的

CUDA:Supercomputing for the Masses (用于大量数据的超级计算)-第一节

原文链接 第一节 CUDA 让你可以一边使用熟悉的编程概念,一边开发可在GPU上运行的软件. Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员.他在多个国家级的实验室进行大型并行运算的研究,并且是几个新创企业的合伙人.大家可以发邮件到[email protected]与他沟通和交流. 您是否有兴趣在使用高级语言(比如C编程语言)编程时,通过标准多核处理器将性能提升几个数量级?您是否期待拥有跨多个设备的伸缩能力