第4章 向量、SIMD和GPU体系结构中的数据级并行

4.1 引言

有多少应用程序拥有大量的数据级并行DLP?SIMD分类Flyn被提出后5年。答案不仅包括科学运算中的矩阵运算,还包括面向多媒体的图像和声音处理以及机器学习算法。

由于SIMD可以执行多个数据操作,能效比MIMD要高,使得SIMD对于个人移动设备和服务器极具吸引力。与MIMD相比,SIMD的最大优势可能是:程序员可以继续采用顺序思维方式,但通过并行数据操作来获得并行加速比。

本章介绍SIMD的三个变体:向量体系结构、多媒体SIMD指令集扩展和GPU。

第一种变体的出现比其他2种要早30多年。向量体系结构在流水线执行的基础上扩展了多种数据操作的能力,与其他的SIMD变体相比,向量体系结构更易理解和编译,但过去人们一直认为它们对于微处理器来说太贵了。成本包括:一部分用在晶体管,另一部分用在提供足够的DRAM带宽,因为它广泛依赖缓存来满足传统微处理器对存储器的性能要求。

第二种SIMD变体借用SIMD名称来表示同时进行的并行数据操作。x86体系的SIMD指令扩展始于1996年的MMX,之后10年间出现几个SSE版本,一直发展到今天的AVX。

SIMD的第三种变体来自于GPU舍去,它的潜在性能要高于当今的传统多核计算机。

这三种变体有一个共同优点,比MIMD编程更容易。

本章的目的让架构师理解为什么向量体系结构比多媒体SIMD指令更具一般性,以几向量体系结构和GPU体系结构的异同。由于向量体系结构是多媒体SIMD指令的超集(包括一个更好的编译模型),且GPU与向量体系结构有一些相似之处,所以先介绍向量体系结构。

4.2 向量体系结构

执行可向量化应用程序的最高效方法就是向量处理器。

向量体系结构从存储器中获取数据元素集,将它们放在大型顺序寄存器堆中,对这些寄存器堆中的数据进行操作,然后将结果放回存储器中。

这些大型寄存器堆相当于编译器控制的缓冲区,即隐藏了存储器延迟,又充分利用了存储带宽,由于向量载入和存储是深度流水化的,所以程序仅在每次向量载入或存储操作中产生一次较长的存储器延迟时间,而不是在载入或存储每个元素时都需要这一时间,从而将延迟分散在多个(例如32个)元素上。

功耗壁垒使架构师转向:能够提供良好的性能,但又不需要像乱序超标量处理器那样,在能耗和设计复杂度方面付出高昂代价。向量指令与这一趋势自然合拍,因为架构师可以利用它们来提高简单顺序标量处理器的性能,同时又不会大幅增加能量要求和设计复杂度。

4.2.1 RV64V扩展

(向量扩展本身称为RVV, RV64V指的是RISV-V基础指令加上向量扩展)

第4章 向量、SIMD和GPU体系结构中的数据级并行_第1张图片

RV64V指令集体系结构的主要组件如下:
        (1)向量寄存器:每个向量寄存器保存一个向量,RV64V有32个向量寄存器,寄存器宽度为2048位。向量寄存器对需要提供足够的端口来填满全部向量功能单元。在针对不同向量寄存器执行的向量操作之间,这些端口允许高度重叠。利用一对交叉开关将这些读写端口连接到功能单元的输入或输出,共有至少16个读端口和8个写端口。增加寄存器堆带宽的一种方法是由多个存储体来组成这一寄存器堆,当向量足够长时,方法特别有效。
        (2)向量功能单元:每个单元都完全流水化,因此它可以在每个时钟周期开始一个新的操作。需要一个控制单元来检测冒险,既包括功能单元的结构冒险,也包括寄存器访问的数据冒险。
        (3)向量载入和存储单元:向量存储器从存储器中载入向量,或者将向量存储到存储器中。假想的RV64V实现中,向量载入和存储操作是完全流水化的,所以在初始延迟之后,可以在向量寄存器与存储器之间以每个时钟周期一个字的带宽移动字。这个单元还会处理标量载入和存储。
        (4)标量寄存器集合:标量寄存器可以将数据作为输入提供给向量功能单元,还可以计算传送给向量载入和存储单元的地址。它们是RV64G的31个通用寄存器和32个浮点寄存器。在从标量寄存器堆读取标量值时,向量功能单元的一个输入会锁住这些值。

尽管传统的向量体系结构不能高效地支持较窄的数据类型,但向量能够自然地容纳不同的数据大小。因为向量数据类型宽度的多样性,向量体系结构不只是科学计算程序有用,对于多媒体应用程序也有用。

RV64V的一项创新就是将数据类型和数据大小与每个向量寄存器关联起来,而不是像传统设计中一样,在指令中提供这一信息。因此,在执行向量指令之前,程序需要配置向量寄存器,用于指定它们的数据类型和宽度。

采用动态寄存器类型设定的一个原因是,为了支持数据类型和位宽的多态性,传统向量指令集中的指令数量非常庞大。

动态类型设定还可以让程序禁用那些没有用到的向量寄存器。好处在于允许将所有向量存储器分配给已启用的向量寄存器用作长向量。不过最大向量长度由处理器设定,不能由软件更改。

人们对向量体系结构有一点抱怨:它们的状态较大,意味着上下文切换时间较长。动态寄存器类型设定有一个让人开心的副作用,那就是当一些向量寄存器不被使用时,程序可以将它们设置为禁用,这样就不需要在上下文切换时候保存和恢复它们了。

动态寄存器类型设定的第三个好处是,在不同大小的操作数之间进行转换时,可以利用寄存器的配置来隐式完成,而不需要另外增加显式转换指令。

利用一个向量指令,系统可以采用多种方式对向量数据进行操作,包括同时对许多元素执行操作。这种灵活性使向量设计能够使用慢而宽的执行单元,以低功耗获得高性能。此外,由于一个向量指令集内部各元素是相互独立的,所以在增加并行的功能单元时,就不需要像超标量处理器那样,二外执行成本高昂的依赖性检查。

4.2.2 AXPY例子

向量代码和标量代码之间最显著的区别是,向量处理器大幅缩减了动态指令带宽,仅执行8条指令,而RV64G要执行236条指令。发生这一缩减的原因是因为向量运算中对32个元素执行的,而在RV64G中差不多占一半循环的开销指令在RV64V代码中不存在。如果循环的迭代之间不存在相关性,那么这些循环就可以向量化。

RV64V和RV64G之间的另一个重要区别是流水线互锁的频率。在向量处理器中,每个向量指令只会因为等待每个向量的第一个元素而停顿,后续元素会沿着流水线顺畅流动。向量架构师将元素相关操作的前递称为链接,因为这些相关操作是被链接在一起的。软件流水线、循环展开或乱序执行可以减少RV64G中的流水线停顿,但很难大幅缩减指令带宽方面的巨大差距。

4.2.3 向量执行时间

向量运算序列的执行时间主要取决于3个因素:
        (1)操作数向量的长度;
        (2)操作之间的结构冒险;
        (3)多个向量操作间的数据依赖关系;

给定向量长度和启动速率(向量单元接受新操作数或生成新结果的速率),可以计算出一条向量指令的执行时间。

为了简化关于向量执行和向量性能的讨论,我们使用护航指令组convoy概念,它是一组可以一起执行的向量指令。护航指令组中的指令不能包含任何结构冒险,否则需要再不同的护航指令组中先后启动这些这些指令。

除了具有结构冒险的向量指令序列之外,具有写后读相关冒险的序列也应该位于单独的护航指令组中。然而,链接chaining操作可以让她们位于同一护航指令组中,因为链接操作允许向量操作在其向量源操作数的各个元素可用时立即启动:链中第一个功能单元的写过被bypass给第二个功能单元。实践中经常采取以下方式来实现链接:允许处理器同时读写一个特定的向量寄存器,不过读写的是不同元素。早期的链接实现类似于标量流水线中bypass,但这限制了链接源指令与目标指令的时序。最近的链接实现采用灵活链接,这种方式允许向量指令链接到几乎任意其他活动的向量指令,只要不生成结构冒险就行。

为了将护航指令组转换为执行时间,需要一种度量来估计护航指令组的长度。这种长度被称为钟鸣clime,就是执行一个护航指令组所需的时间单位。因此,执行m个护航指令组序列需要m个钟鸣。当向量长度为n时,对于简单的RV64V而言,大约为m*n个时钟周期。

钟鸣近似值忽略了处理器特有的一些额外开销,其中许多开销依赖于向量长度。因此,以钟鸣为单位测量时间时,对于长向量的近似要优于对短向量的近似。

另一个额外时间开销要比指令发射数量的限制重要得多。钟鸣模型忽略的最重要的开销是向量启动延迟,即向量功能单元的流水线被向量指令填满之前,以时钟周期为单位的延迟。启动延迟主要由向量功能单元的流水线延迟决定。

优化性能或增加在向量体系结构中良好运行的程序类型的问题有?
        1. 向量处理器怎样执行单个向量才能在每个时钟周期执行多于一个元素?
        2. 向量处理器如何处理那些向量长度与最大向量长度不匹配的程序?
        3. 如何处理要向量化的代码中含有if语句?
        4. 向量处理器需要从存储器系统获取什么?
        5. 向量处理器如何处理多维矩阵?
        6. 向量处理器如何处理稀疏矩阵?
        7. 如何为向量计算机编程?如果结构创新不能与编程语言及其编译器技术相匹配,就不可能得到广泛应用。

4.2.4 多条通道:每个时钟周期处理多个元素

向量指令集的一个关键好处是,软件仅使用一条很短的指令就能向硬件传送大量并行任务。向量指令的并行语义允许实现使用一个深度流水化的功能单元、一组并行功能单元,或者并行功能单元与流水线功能单元的组合。

RV64V指令集有一个特性:所有向量算术指令只允许一个向量寄存器的第N个元素与其他向量寄存器的第N个元素进行运算。这一特性极大地简化了高度并行向量单元的设计,该单元可以构建为多个并行通道。可通过添加更多通道来提高向量单元的峰值吞吐量。若想让多通道带来优势,应用程序和体系结构都必须支持长向量;否则,它们会快速执行,耗尽指令带宽,并需要ILP技术提供足够的向量指令。

每条通道都包含向量寄存器堆的一部分以及来自每个向量功能单元的一条执行流水线。依次将向量长度的元素分配给各个通道,使得该通道本地的算术流水线无需与其他通道通信就能完成运算。通过避免通道间的通信,减少了构建高并行执行单元所需要的连接成本与寄存器堆端口,同时也解释了向量计算机为什么能够在每个时钟周期内完成多达64个运算(16通道,每条通道包含2个算术愿挨和2个载入和存储单元)。

增加多条通道是提高向量性能的一种常用技术,它几乎不需要增加控制复杂性,也不需要堆现有机器代码进行修改。它还允许设计人员在晶片面积、时钟频率、电压和能耗之间进行权衡,而且不需要牺牲峰值性能。如果向量处理器的时钟频率减半,那么将向量通道数目加倍就能保持原峰值性能。

4.2.5 向量长度寄存器:处理非最大向量长度整数倍的循环

向量寄存器出全力有一个自然向量长度,这一长度由最大向量长度决定mvl,该长度很难与实际程序中的长度相匹配。此外,实际程序中的向量运算的长度在编译时通常是未知的。

这一问题的解决方案是添加一个向量长度寄存器vl。vl控制所有向量运算的长度,包括向量载入和存储运算。但vl中的值不能大于mvl。这个参数意味着向量寄存器的长度可以随着计算机的发展而增大,而不需要改变指令集。多媒体扩展SIMD扩展没有与mvl相对应参数,所以每次增大向量长度时,它们都会扩展指令集。

如果n值的在编译时未知,可以使用一种条带挖掘strip mining的技术。条带挖掘是指生成一些代码,使每个向量运算都是针对向量大小小于或等于mvl的情况来完成的。一个循环处理迭代数为mvl倍数的情况,另一个循环处理剩下的迭代,这些迭代数量必须小于mvl。

4.2.6 谓词寄存器:处理向量循环中的IF语句

中低度向量化程序的加速比非常有限。循环内部存在条件与使用稀疏矩阵是向量化较低的两个主要原因。循环中包含IF语句的程序无法使用前面讨论的技术以向量模式运行,因为IF语句会在循环中引入控制相关。

实现这一功能的常见扩展称为向量掩码控制。在RV64V中,谓词寄存器保存此掩码,为一条向量指令中的每个元素运算提供了条件执行方式。这些寄存器使用一个布尔向量来控制向量指令的执行,就像条件执行使用布尔条件来判断是否要执行一个标量指令一样。当谓词寄存器p0被置位时,所有后续向量指令都仅针对一部分向量元素执行,这些元素在谓词寄存器中的对应项为1。如果目标向量寄存器中的某些项在谓词寄存器中的对应值为0,那它们就不会受到向量运算的影响。

采用向量掩码执行的向量指令仍然需要相同的执行时间,即使掩码为0的元素也是如此。

向量处理器与GPU之间的一个区别是处理条件语句的方式。向量处理器将谓词作为体系结构状态的一部分,并且依靠编译器来显式地操作掩码寄存器。而GPU使用硬件来控制GPU软件无法看到的内部掩码寄存器,以实现相同效果。在这两种情况下,无论相应的掩码位是1还是0,硬件都要花时间执行向量元素,所以GFLOPS速率在使用掩码时会下降。

4.2.7 存储体:为向量载入/存储单元提供带宽

载入/存储向量单元的行为要比算术功能复杂得多。载入操作的启动延迟就是它从存储器向寄存器中载入第一个字的时间。如果可以在无停顿的情况下提供向量的其他元素,那么向量启动速率就等于提取或存储新字的速度。这一启动速率不一定是一个时钟周期,因为存储体的停顿可能会降低实际吞吐量。

为了保持每个时钟提取或存储一个字的启动速率。存储器系统必须能够提供或接受较多的数据。将访问对象分散在多个独立的存储体中,通常可以保证所需速率。

大多数向量处理器使用存储体的原因有三:
        1. 许多向量计算机支持每个时钟周期执行多个载入或存储操作,访问存储体的周期时间通常比处理器周期时间高几倍。为了支持多个载入或存储操作的同时访问,存储器系统需要有多个存储体,还要能够独立控制对这些存储体的寻址。
        2. 大多数向量处理器支持载入或存储非连续的数据字。在这种情况下,需要进行独立的组寻址,而不是交叉寻址。
        3. 大多数向量计算机支持多个处理器共享同一存储器系统,所以每个处理器会生成其自己的独立寻址流。

4.2.8 步幅:处理向量体系结构中的多维数组

向量中的相邻元素在存储器中的位置不一定是连续的。多维数组分配存储器时,数组要被线性化,意味着行中的元素或者列中的元素在存储器中是不连续的。

对于没有缓存的向量处理器,需要用另一种方法来提取在存储器中不相邻的向量元素。它们之间的距离称为步幅stride。

一旦将向量载入向量寄存器,它的表现就好像它的元素在逻辑上是相邻的。因此,仅利用具有步幅功能的向量载入及向量存储操作,向量处理器就可以处理大于1的步幅,这种步幅称为非单位步幅。向量处理器的一大优势就是能够访问非连续存储地址,并将其重组成一个稠密的结构。

缓存在本质上是处理单位步幅数据的。增加块大小有助于降低大型科学数据集(步幅为单位步幅)的缺失率,但增大块大小也可能会对那些以非单位步幅访问的数据产生负面影响。支持大于1 的步幅会使存储器系统变得复杂。一旦引入非单位步幅,就可能频繁访问同一个存储体。

        bank数/(步幅与bank数的最大公约数) < bank繁忙时间

4.2.9 集中-分散:在喜爱拿过来体系结构中处理稀疏矩阵

在稀疏矩阵中,向量元素通常是以某种压缩形式存储的,然后被间接访问。支持稀疏矩阵的主要机制是采用索引向量的集中-分散操作。这种操作的目的是支持在稀疏矩阵的压缩表示(即不包含0)和正常表示(即包含0)之间进行转换。集中操作取得索引向量,并在此向量中提取元素,元素位置等于基础地址加上索引向量中给定的偏移量。其结果是向量寄存器中的一个密集向量。在以密集形式对这些元素进行操作之后,可以再使用同一索引向量,通过分散存储操作,以扩展方式存储该稀疏向量。对于此类操作的硬件支持称为集中-分散,几乎所有现代向量处理器都具备这一功能。

简单的向量化编译器无法自动将以上源代码向量化,因为编译器不知道索引向量的元素是不同的值,因此也就不存在相关性。所以,需要程序员通过显式地在代码中指示编译器,可以放心地以向量化模式运行这个循环。

尽管索引载入和存储(集中与分散)操作都可以流水化,但由于存储体在开始执行指令时是未知的,所以它们的运行速度通常远低于非索引载入或存储操作。寄存器堆还必须在向量单元的通道之间提供通信,以支持集中和分散操作。

执行集中和分散操作的每个元素都有各自的地址,所以不能对它们进行分体处理,而且在存储器系统的许多位置可能存在冲突。因此,即使在基于缓存的系统上,每次访问也会造成严重的延迟。

在GPU中,所有载入都是集中操作,所有存储都是分散操作,因为没有单独的指令限制地址必须是连续的。为了将可能较慢的集中和分散操作转换为更高效的存储器单位步幅访问,GPU硬件在执行期间识别顺序地址,并且GPU程序员必须确保一次集中或分散操作中所有地址都位于相邻位置。

4.2.10 向量体系结构编程

向量体系结构的优势在于,编译器可以在编译时告诉程序员某段代码是否会向量化,通常还会给出一些提示,说明这段代码为什么没有向量化。这种简单的执行模型可以让其他领域的专家快速掌握修改代码来提高性能的方法,并提示编译器特定操作(比如集中-分散式的访存请求)间不存在依赖关系以提高性能。

今天,影响程序在向量模式下能否成功运行的主要因素是程序本身的结构:循环是否有真正的数据相关?能否调整它们的结构,使其没有此类相关?这一因素受算法选择的影响,在一定程度上还受编码方式的影响。

4.3 多媒体SMID指令集扩展

多媒体SIMD扩展源于一个很容易观察到的事实:许多多媒体应用程序操作的数据类型要比对32位处理器进行针对性优化的数据类型更窄一些。图形系统使用8位来表示3基色的每一种颜色,在用8位来表示透明度。音频采样通常使用8位或16位来表示。假定有一个256位加法器,通过划分这个加法器中的进位链,处理器可以同时对一些短向量进行操作,这些向量可以是32个8位操作数,16个16位操作数,8个32位操作数或者4个64位操作数。这些经过划分的加法器的额外成本很小。和向量指令一样,SIMD指令指定了对数据向量的操作。不过SIMD指令的指定的操作数往往较少,一次使用的寄存器堆也小得多。

向量体系结构提供了一个优雅的指令集,可以被向量化编译器充分开发。SIMD扩展则不同,它有3项缺失:没有向量长度寄存器,没有步幅或集中/分散数据传送指令,没有掩码寄存器。
        1.    多媒体SIMD扩展固定了操作代码中数据操作数的数量,从而在x86体系结构的MMX、SSE和AVX扩展中添加书白条指令。向量计算机中的变长向量寄存器可以轻松适应那些向量长度小于体系结构支持的最大长度的程序。此外,向量体系结构有一个隐含的最大向量长度,它与向量长度寄存器相结合,从而避免了为了支持不同向量长度的指令占用。
        2. 直到最近,多媒体SIMD还没有提供向量体系结构中以步幅访问和集中-分散访问为代表的复杂寻址模式。这些访存模式增加了向量编译器能够轻松成功向量化的程序数目。
        3. 多媒体SIMD通常不会像向量体系结构那样,为了支持元素的条件执行而提供掩码寄存器。不过这种情况正在改变。

这三项缺失增大了编译器生成SIMD代码的难度,也加大了SIMD汇编语言的难度。

对于X86结构,1996年增加了MMX指令,1999年推出的SIMD扩展SSE,2001年的SSE2,2004年的SSE3和2007的SSE4,2010年增加的高级向量扩展AVX再次将寄存器宽度增加了一倍。2017年AVX-512诞生、

既然有了这些弱点,多媒体SIMD扩展为啥还如此流行:
        1. 它们不需要花费什么成本就能添加标准算术单元,而且易于实现;
        2. 与向量体系结构相比,它们不需要额外的处理器状态,从而降低了上下文切换的代价;
        3. 需要大量存储带宽来支持向量体系结构,而这是许多计算机不具备的;
        4. 当一条指令能够生成32个存储器访问,而且任何一个访问都能引发缺页错误时,SIMD不必处理虚拟存储器中的问题。SIMD扩展对于操作数的每个SIMD组使用独立的数据传送,所以它们不能跨越页面边界。
        5. 固定长度的简短SIMD“向量”还有另一个好处:能够轻松地引入一些符合新媒体标准的指令,比如执行置换操作的指令,或者所用操作数少于或多余向量能生成的操作数的指令。
        6. 最主要的是,由于二进制兼容性的重要性,一旦架构开始使用SIMD,就很难拜托它。

4.3.1 多媒体SIMD体系结构的编程方法

鉴于SIMD扩展的特殊性,使用这些指令最简便方法就是通过库或用汇编语言编写。

最近的扩展变得更加频繁,使得编译器可以理解SIMD扩展指令集。通过借用向量化编译器的技术,这些编译器也开始自动生成SIMD指令。但是,程序员必须确保存储器中的所有数据都与运行代码的SIMD单元的宽度对齐,以防止编译器为本来可以向量化的代码生成标量指令。

4.3.2 rootline模型

几种内核的相对运算密度:

第4章 向量、SIMD和GPU体系结构中的数据级并行_第2张图片

可实现的峰值GFLOP/s=min(峰值带宽存储 * 运算密度, 峰值浮点性能)

屋檐根据内核的运算密度设定了其内核性能上限。如果脊点非常靠右,那么只有运算密度非常高的内核才能实现这台计算机的最大性能。如果它非常靠左,那么几乎所有内核都可以达到最高性能。与SIMD处理器相比,向量处理器的存储带宽要高得多,并且脊点非常靠左。

4.4 GPU

CPU和GPU在计算机体系结构谱系中不会上溯到同一个祖先;并没有那个“过度环节”可以解释这两者之间的关系。

4.4.1 GPU编程

CPU程序员的挑战不只是在GPU上获得出色的性能,还有协调系统处理器核GPU上计算调度,以及系统存储器与GPU存储器之间的数据传输。GPU几乎拥有所有可以由编程环境捕获的冰箱类型:多线程、MIMD、SIMD、甚至是指令级并行。

并行计算编程模型设计中存在一对难以调和的矛盾:一方面要提高程序员的开发效率;另一方面允许程序员表达出硬件支持的一切功能。

第4章 向量、SIMD和GPU体系结构中的数据级并行_第3张图片

和向量体系结构一样,GPU只能很好地解决数据级并行问题。这两种体系结构类型都拥有集中-分散数据传送和掩码寄存器,并且GPU处理器的寄存器比向量处理器更多。与大多数向量体系结构不同的是,GPU还依靠单个多线程SIMD处理器中的多线程来隐藏存储器延迟。GPU架构师的一个基本假设是GPU上应用程序有足够多的SIMD线程,多到既可以隐藏到DRAM的延迟,又可以提高多线程SIMD处理器的利用率。

单个线程所使用的寄存器数量与实际运行的线程数目是一个权衡关系。

与向量体系结构不同,GPU没有分别用于顺序数据传送、步幅数据传送和集中-分散数据传送的指令。所有数据传送都是集中-分散的!为了重新获得顺序(单位步幅)数据传送的效率,GPU包含了特殊的“地址接合”硬件,用于判断SIMD线程中的SIMD通道什么时候一同发出顺序地址。运行时硬件随后通知存储器接口单元请求32个顺序字的分块传送。为了实现这一重要的性能改进,GPU程序员必须确保同时访问附近的地址,以便将它们接合为一个或几个存储器或缓存块。

4.4.4 GPU中的条件分支

除了显式谓词寄存器之外,GPU分支硬件使用了内部掩码,分支同步栈和指令标记来控制分支何时分为多个执行路径以及这些路径何时回汇合。

在GPU硬件指令级别,控制流包括分支、跳转、索引跳转、调用、索引调用、返回、退出和管理分支同步栈的特殊指令。GPU硬件为每个SIMD线程提供了它自己的栈:一个栈顶包含一个标识符标记、一个目标指令地址和一个活跃线程掩码。GPU硬件指令还有为不同通道设置的不同谓词。

PTX汇编器通常把简单的IF-THEN-ELSE语句优化为只设有谓词的GPU指令,而不需要生成任何GPU分支指令。更复杂的控制流的优化通常会混合使用谓词和GPU分支指令。当某些通道跳转到目标地址,而其他通道失败时,这些指令会在分支同步栈栈顶压入一项。

在所有通道完成循环之后,使这些SIMD通道汇合。GPU索引跳转和索引调用指令向栈中压入条目,以便在所有通道完成switch语句或函数调用,SIMD线程汇合。

对于相同长度的路径,IF-THEN-ELSE的工作效率为50%或更低。

深度嵌套可能意味着大多数SIMD通道在嵌套条件执行语句期间是空闲的。

在一个时钟周期内,SIMD通道的唯一选择就是执行PTX指令中指定的操作或者处于空闲状态;两条通道不能同时执行不同指令。

条件执行时,GPU在运行时用硬件完成它,而向量体系结构则在编译时完成。向量体系结构有一个好处,就是可以与标量处理器集成在一起。

什么时候最好使用标量,要取决于标量处理器与向量处理器的速度之比,但当掩码中不到20%为1时,进行这种转换可能更好。

GPU执行条件分支的效率取决于分支的分岔频率。

4.4.5 NVIDIA GPU存储器结构

第4章 向量、SIMD和GPU体系结构中的数据级并行_第4张图片

每个线程获得片外DRAM的一个专用部分,称为private memory,用于栈帧、溢出寄存器和寄存器中放不下的私有变量。如果某些数据会被同一个线程或同一个线程块的另一个线程重复使用,则将其放在共享内存上。SM在创建线程块时,将部分共享内存动态分配给此线程块,并在线程块中的所有线程都退出时,释放此存储。

GPU通常不是依赖大型缓存来包含应用程序的整个工作集,而是使用较小的流式缓存,依靠大量的SIMD指令多线程来隐藏DRAM的较长延迟,其主要原因是它们的工作集可能达到数百兆字节。在利用多线程隐藏DRAM延迟的情况下,CPU上供大型L2和L3缓存使用的芯片面积在GPU上被用于计算资源和大量的寄存器,从而使得GPU可以保存许多线程的状态。此外,CPU和GPU另一个不同之处在于向量的载入和存储将延迟分摊到多个元素上,因为它们只需要有一次延迟,随后即可实现其余访问的流水化。

尽管GPU和向量处理器的最初思路都是利用许多线程来消除存储器延迟的影响,但近来所有GPU和向量处理器都使用了缓存来缩减延迟。论据来自little定律:延迟越长,在一次存储访问期间需要运行的线程就越多,而这又需要更多的寄存器。于是增加爱GPU换出来降低平均延迟,从而弥补了寄存器数量方面可能存在的不足。

为了提高存储带宽、降低开销,当地址属于相同块时,PTX数据传送指令会与存储器控制器合作,将来自同一个线程束的各个并行线程请求接合在一起,变为单个存储块请求。

4.4.7 向量体系结构与GPU的相似与不同 第4章 向量、SIMD和GPU体系结构中的数据级并行_第5张图片

GPU的SM充当独立的MIMD核,可以将P100看作一个具有多线程硬件支持的56核,其中每个核有64通道。与向量处理器最大区别是多线程,它是GPU的基础,也是大多数向量处理器所缺少的。

第4章 向量、SIMD和GPU体系结构中的数据级并行_第6张图片

RV64V寄存器堆拥有整个向量,也就是由元素构成的连续块。而GPU中单个向量会分散在所有线程的寄存器中。

向量体系结构采用显式单位步幅载入和存储指令,而GPU编程则采用隐式单位步幅。

向量体系结构通过深度流水化访问,让向量的所有元素分摊这一延迟,所以每次向量载入和存储只需要付出一次延迟代价。GPU则使用多线程隐藏存储器延迟。

关于条件分支指令,两种体系结构都是用掩码寄存器来实现。两个分支路径近视在未存储结果时也会占用时间或者空间。区别在于,向量编译器用软件显示管理掩码寄存器,而GPU硬件和汇编器则使用分支同步标记来隐式管理它们,使用内部栈来保存、求补和恢复掩码。

向量计算机的控制处理器在向量指令执行过程中扮演重要角色。它向所有向量通道广播操作,并广播用于向量-标量运算的标量寄存器值。它还执行一些在GPU中显式执行的隐式计算,比如自动为单位步幅和非单位步幅载入和存储指令递增存储器地址。GPU中的运行时硬件机制一方面生成地址,另一方面还会查看它们是否相邻。

向量计算机中的标量处理器执行向量程序的标量指令。向量计算机中比较简单的标量处理器很可能比GPU解决方案更快、功效更高。

4.4.8 多媒体SIMD计算机与GPU的相似与不同

第4章 向量、SIMD和GPU体系结构中的数据级并行_第7张图片

        1. 二者都是多处理器,处理器都使用多条SIMD通道,只不过GPU的处理器更多,通道数则还要多一些。
        2. 它们都是用硬件多线程来提高处理器利用率。二者的浮点运算峰值比值约2:1。
        3. 都使用缓存,不过GPU使用流式缓存。
        4. GPU的主存小得多。都支持页面级别的存储器保护和按需调页,因而可以处理比现有存储器大得多的存储器。
        5. 多媒体SIMD指令不支持集中-分散存储器访问。

4.4.9 小结

与传统多核计算机相比,GPU处理器更多,每个处理器的通道数更多,多线程硬件也更多,缓存存储器更少。

第4章 向量、SIMD和GPU体系结构中的数据级并行_第8张图片

第4章 向量、SIMD和GPU体系结构中的数据级并行_第9张图片

4.5 检测与增强循环级并行

本节讨论用于发现程序中可以利用的并行的编译器技术,以及这些技术的硬件支持。我们准确地定义一个循环何时是并行的(即可向量化的)、相关性如何阻碍循环成为并行的,以及用于消除相关性的技术。发现和利用循环级并行,对于利用DLP和TLP以及附录H介绍更激进的静态ILP方法都至关重要。

循环级并行通常是在源代码级别或接近源代码级别进行研究,而对ILP的大多数分析是在编译器生成指令之后进行的。

循环级并行的分析主要判断后续迭代中的数据访问是否依赖于先前迭代生成的数据值,这种相关称为跨迭代相关。

跨迭代相关经常是递推形式,即一个变量基于它在先前迭代中的取值进行定义时,就会发生递推。检测递推非常重要:
        1. 一些体系结构(特别是向量计算机)对执行递推提供了特殊支持;
        2. 对于ILP而言,递推形式的跨迭代相关有可能并不成为开发并行性的阻碍。

4.5.1 查找相关

高级语言中的指针和引用参数会增加分析过程的复杂性和不确定性。编译器是如何检测相关的?几乎所有相关分析算法都假定数组索引是仿射的affine,即线性变换。要判断一个循环中对同一数组的两次引用之间是否存在相关,等价于判断两个仿射函数能否针对不同索引取同一个值。

相关性分析时检测循环级别并行的一种基本工具,缺点是它仅适用于非常有限的一些情况,也就是用于分析单个循环嵌套中引用之间的相关以及使用仿射索引功能的情景。

4.6 交叉问题

4.6.1 能耗与DLP:慢而宽与快而窄

数据级并行体系结构的低功耗优势来自于第1章的能耗公式。假定有充足的数据级并行,那么如果将时钟频率折半并将执行资源加倍(将向量计算机的通道数加倍,将多媒体SIMD的寄存器和ALU加宽,增加GPU的SIMD通道数),性能将是一样的,如果在降低时钟频率的同时降低电压,就可以降低计算过程的能耗和功耗,同时保持峰值性能不变。因此,GPU的时钟频率往往低于CPU,后者依靠时钟频率来获取性能。

与乱序处理器相比,DLP处理器可以采用较简单的控制逻辑,在每个时钟周期中启动大量计算。例如,这一控制对于向量处理器中所有通道都是相同的,没有用于决定多指令发射的逻辑和推理执行逻辑。它们获取和译码的指令也少得多。利用向量体系结构还可以轻松地关闭芯片中未使用的部分。在发射指令时,每条向量指令都明确指明它在大量周期内所需要的全部资源。

4.6.2 分体存储器和图形存储器

为了实现最佳存储器性能,GPU都使用了HBM,它们堆叠在一起,并与计算芯片放置在同一个封装内。大宽度(1024~4096条数据线)提供了高带宽,而将存储芯片和处理芯片放在同一封装内,缩短了延迟,降低了延迟。HBM的容量通常是8~32GB。

考虑到计算任务和图形任务时,存储器系统可能会面对大量的不相干请求。GPU的存储器控制器为不同存储体各维护一个汇聚存储器请求的队列,从而在等待有足够多的请求后再打开对应的DRAM行,并一次传输所有请请求的数据。这一等待提升了带宽,但使延迟时间增长,而控制器必须确保所有处理单元不会因为等待数据而“挨饿”,否则,相邻的处理器可能会处于空闲状态。与基于缓存的传统体系相比,集中-分散技术和可感知存储器-存储体的访问技术可以大幅提升性能。

4.6.3 步幅访问和TLB缺失

步幅访问的一个问题是它们如何与TLB交互,以在向量体系结构或GPU中访问虚拟存储器(GPU也使用TLB来实现存储器映射) 。根据TLB的组织方式以及存储器中受访数组的大小,甚至有可能在每次访问数组元素时都会遇到一次TLB缺失。缓存也可能发生相同类型的冲突,但性能影响可能更小。

4.7 融汇贯通

第4章 向量、SIMD和GPU体系结构中的数据级并行_第10张图片

第4章 向量、SIMD和GPU体系结构中的数据级并行_第11张图片

鉴于GPU性能比CPU性能快,Intel研究人员表示:
        1. 存储带宽。GPU存储带宽高。
        2. 计算带宽。GPU计算带宽高。
        3. 缓存优势。
        4. 集中--分散。GPU提供集中-分散寻址,向量体系结构中也使用了这一技术,但SIMD扩展没有。地址接合单元也可以提供帮助:合并对相同缓存行的访问,从而减少集中于分散的数目。存储器控制单元还会对相同的DRAM页面进行批访问。
        5. 同步。

不过,该对比缺少一个重要的特性,即描述为获得两个系统的结果所付出的努力。

4.7.2 对比更新

Intel在AVX2增加了集中指令,在AVX/512中增加了分散指令。

4.8 谬论和易犯错误

谬论:GPU因作为协处理器而“窘迫”。
        尽管主存储器与GPU之间的分割有缺点,但于CPU保持一定距离是有好处的。例如,PTX之所以存在,部分原因是作为GPU的IO设备非常灵活。这种编译器于硬件之间的间接性给了GPU架构师比系统处理器架构师更大的灵活性。PTX允许GPU架构师尝试创新,如果令人失望,在后续几代放弃这些创新。而对于CPU而言,包含一项创新需要更充分的理由--因此可以进行的实验也少得多--因为发行二进制机器码通常意味着新特性必须得到该体系结构的所有后代的支持。

对PTX价值的一个证明,不同代的架构从根本上改变了硬件指令集--从像x86这样的面向存储器指令变成了像RISC-V这样面向寄存器的指令集,并将地址大小增大了一倍,达到64位--但没有破坏NVIDIA的软件栈。

易犯错误:关注向量体系结构的峰值性能,忽略启动开销。

易犯错误:提高向量性能,却没有相应地提高标量性能。
        即使到了今天,向量性能较低但标量性能较佳的处理器,也要优于峰值向量性能较佳的处理器。良好的标量性能可以降低开销成本,并降低Amdahl定律的影响。

谬论:可以在不提高存储带宽的情况下获得良好的向量性能。

存储带宽对于所有SIMD体系结构都非常重要。

谬论:在GPU上,如果存储器性能不够高,只需添加更多线程就可以了。

GPU使用多线程来隐藏主存储器的延迟的前提是存储器访问是连续的。

4.9 结语

数据级并行对于个人移动设备越来越重要,因为这些设备上显示音频、视频和游戏重要性的应用程序越来越流行。当与一个比任务级并行更容易编程且可能具有更高能效的模型相结合时,就很容易理解为什么在这十年中数据级并行出现了复兴。

传统处理器与GPU在性能方面最大的差别之一是集中--分散寻址。

GPU的问题不只是哪种体系结构最好,而是当硬件投入足以出色地完成图形处理时,如何增强它以支持更具一般性的计算任务?尽管向量体系结构在理论上具有许多优点,但它能否像GPU一样作为图形学的基础还有待证明。RISC-V已经接受了向量而不是SIMD。

你可能感兴趣的:(计算机体系结构,量化研究方法,第6版,计算机体系结构,并行计算,性能优化)