《高性能科学与工程计算》——1.2 基于高速缓存的通用微处理器体系结构-阿里云开发者社区

开发者社区> 华章出版社> 正文
登录阅读全文

《高性能科学与工程计算》——1.2 基于高速缓存的通用微处理器体系结构

简介:

本节书摘来自华章计算机《高性能科学与工程计算》一书中的第1章,第1.2节,作者:(德)Georg Hager Gerhard Wellein 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

1.2 基于高速缓存的通用微处理器体系结构

微处理器可能是人类发明的最复杂的机器。然而,就像前面章节中描述的那样,它们都基于存储程序数字计算机的概念。对于科学家而言,理解CPU所有内部工作细节是不可能的,也是不必要的,尽管把握其高级特性对于了解潜在的性能瓶颈是有帮助的。图1-2展示了现代基于高速缓存的通用微处理器的简图。对于一个运行的程序,真正执行计算的部分是仅占芯片一小部分的浮点型(FP)和整型(INT)计算单元。其他逻辑控制单元用来向计算单元提供操作数。一般将CPU寄存器区分为浮点数和整数两种类型,CPU寄存器能够存放指令访问所需操作数,这样在数据访问时没有明显的延时。在一些体系结构中,所有算术运算的操作数都必须存放在寄存器中。如今,主流CPU有16到128个两种类型的寄存器。Store(ST)和Load(LD)单元执行寄存器存取指令,待执行指令被保存到多个队列中,这些指令可能会被乱序执行。最后,高速缓存保存即将执行的指令和数据。而芯片的主要部分就是高速缓存。


d6a0a454b9d83a47d8a44eb42f1efd30fae12fe5

许多额外逻辑,包括分支预测、缓冲区重排、数据旁路、事务队列等,已经应用到现代微处理器中,在此我们不进行讨论。供应商提供了许多文档描述这些技术细节 [V104,V105,V106]。在过去十年中,多核处理器已经取代了传统单核处理器设计模式。在多核芯片上,多个处理器(“核”)同时执行代码。它们能够像存储器接口或高速缓存一样,在不同程度上共享资源,详细内容见1.4节。
1.2.1 性能指标和基准测试
峰值性能是CPU核心所有组件同时执行时的最高性能。一个特定的应用程序能否达到峰值取决于许多因素(详见第3章内容)。这里,我们介绍一些衡量CPU利用率的基本性能指标。科学计算通常使用“双精度”(DP)浮点数运算,FP单元的性能测量标准是每秒执行多少次浮点乘法和加法运算(Flop/s,浮点数运算次数/秒)。由于更复杂运算(除法、平方根、三角函数等)与乘法及加法共享计算资源,并且执行效率很低,对于实际性能衡量没有意义(见第2章),因此高性能软件应该尽量避免调用这样的操作。在本书写作时,标准商用微处理器通常在每个时钟周期最多完成两个或四个双精度浮点计算。在时钟频率为2~3 GHz时,峰值为每核4~12GFlop/s。
如上所述,向运算单元提供操作数是一项复杂的工作。从程序员的角度看,最重要的数据通路是从高速缓存到主存。性能指标用数据带宽即GB/s来衡量。GFlop/s和GB/s通常是衡量微处理器性能的主要指标。因此,如图1-3所示,从一个关注性能的程序员角度看,一个基于高速缓存的微处理器是以数据为中心的,计算或某种算法通常由数据项的操作决定。然而,算法的具体实现受到硬件数据通路的性能限制,特别是主存。


291eab2527f289dc04fe79b4491c7cccd64beb63

通常用低级别的基准测试衡量处理器或者系统的性能特征,即只测量系统中某些特征的程序,比如峰值性能或存储带宽等。一个突出的例子是由Sch?nauer提出的三维向量算法[S5]。它包括一个嵌套循环,循环内执行一个三维向量的复合乘法,并将结果保存在第四个向量中(见代码清单1-1中的10~15行)。该基准测试的目的是衡量处理器的内存和运算单元之间的数据传输性能。在内循环中,3个load流B、C和D以及一个store流A是活跃的,根据N的不同,该循环的时间可能非常短而难以衡量。外部循环再重复R次,以使执行时间变得足够长而能被精确测量。实际中,人们往往会根据N而选择R,从而使得总体的执行时间保持不变。


7e7600651a9d76d3eab54b9497752fa4e64e7446

调用dummy()子程序的目的是阻止编译器优化。没有调用的话,编译器会发现,内层循环和外层循环指数j是完全无关的,从而删除外层循环。对dummy()的调用使得编译器认为,这些数组可能在外层循环之间变换,这有效地防止了优化,并且因为if语句条件总是非真(这个编译器不知道),所以调用dummy()的开销可以忽略不计。
MFLOPS变量是指计算整个循环嵌套的MFlop/s。请注意,在基准测试中最合理的时间单位是墙上时间(wallclock time),也称为逝去时间(elapsed time)。系统提供的任何其他计量时间方式很容易产生误解,因为可能计量了包含I / O、上下文切换、其他进程开销等的时间,这不应该计算在CPU时间之中。对于并行程序时间的计量更是如此(见第5章)。像上面提到的三维向量基准测试中用到的时间计量方法是一个比较常用的方法,如代码清单1-2所示。提供具有和不具有强调功能的两个版本,是因为Fortran常常对子程序名称给予强调。有了这两个有效版本,使得将编译过的C代码链接到Fortran或者C语言主程序上总是可行。


<a href=https://yqfile.alicdn.com/a18bb6f9b7a1a07634adb959ebdba3e7285f9861.png" >

图1-4显示了三维向量算法的测试程序在向量系统和一些不同的基于高速缓存的微处理器上的性能。在非常小的循环上,无论哪种类型的CPU或体系结构计算性能都很差,但是随着N的增大,标准微处理器的计算性能一直增加到最高性能,随后骤然下降。最后,对于非常大的循环,性能保持恒定。这些特性将在1.3节详细分析。
向量处理器(图1-4中的虚线)显示出突出的特征。它的低性能区域延伸得比基于高速缓存的微处理器更远,但是它完全没有等于0。我们得出这样的结论:向量系统对于标准的CPU具有互补性,因为不同的CPU用在不同领域(1.6节详细介绍向量处理器体系结构)。然而在实际的代码优化中,也许能够通过避免低性能区域来提高性能(详细内容介绍见第2章和第3章)。

937f74bf8a30f6b5b9d4d21565d34274148e2b8d

低层次的基准测试是获得处理器基本性能信息的有力工具。然而它们不能够准确地预测出每一个实际应用程序的运行情况。为了确定某些CPU或体系结构是否能够很好地适应一些应用程序(例如,在采购前或在为一个计算机时间写建议前),唯一安全的方法是制定应用基准测试。这意味着,应用程序代码运行时会需要一些输入参数,这些参数能真实地反映实际运行的要求。应该基于大量应用基准测试决定支持或反对一个体系结构。像SPEC这种标准的测试基准只能作为一个粗糙的指南[W118]。
1.2.2 晶体管:摩尔定律
台式机出现之前的计算技术已经被广泛用于具有大量计算需求的科学计算中,在超过30年的时间里,无论哪种技术构造的计算机芯片,它们的复杂性或通用能力每24个月大约翻一倍,这一趋势称为摩尔定律。英特尔公司创始人之一Gordon Moore,在1965年预测一个芯片上可以容纳的最小晶体管数量将会按照预测的速率增长[R35]。尽管制造技术已经发生了质的变化,但这一规律从20世纪60年代早期一直持续到现在。令人惊讶的是,处理器并不是计算机的唯一组成部分,所以处理器性能的意义仍然存在很大争议,但处理器芯片复杂性的增长总是大约等同于计算机性能的增长(下文对这点有更多讨论)。
通过增加芯片中晶体管的数量和时钟频率,处理器的设计者能够实现许多先进的技术,从而使计算机应用性能得到改善。于是产生了许多新的概念:
1)流水线功能单元。在所有关于计算机技术的创新中,流水线也许是最重要的一个。通过将复杂的操作(例如,浮点数加法和乘法)分解成能够在CPU上不同的功能单元执行的简单部分,可以提高指令吞吐量,即增加每个时钟周期执行的指令条数。这是指令级并行(Instruction Level Parallelism,ILP)最基本的例子。最佳的流水线能够得到每个时钟周期一条指令的吞吐量。在本书写作时,处理器的流水线存在30多个阶段。详细介绍见1.2.3节。
2)超标量体系结构。通过使得每个时钟周期中指令的吞吐量大于1,超标量结构直接提供了指令级并行。这需要更多相同的功能单元,使得操作能够同时执行(详细介绍见1.2.4节)。现代微处理器实现了六路超标量体系结构。
3)通过单指令多数据(SIMD)指令达到数据并行。通常针对特殊寄存器上的整型或浮点型向量数组,SIMD(Single Instruction Multiple Data)指令能发出相同的操作。这在不添加超标量技术的情况下也能提升算法性能。例如,英特尔SSE系列机,AMD的3DNow!基于Power和PowerPC处理器扩展的AltiVec,以及Sun公司的UltraSPARC设计中的“VIS”指令集(详细介绍见1.2.5节)。
4)乱序执行。如果指令的操作数不能及时从寄存器中获得,例如存储系统太慢跟不上处理器的速度,此时乱序执行则可以允许执行后续指令流中已经获得参数的指令,从而避免阻塞时间(也叫做stall)。这能提高指令吞吐量,同时使得编译器的代码组织和性能优化更加简单。通过使用重排序缓冲区来存储待执行的指令直到该指令适合执行为止,目前的乱序执行设计在任何时候都能够保持数百条指令的执行速率。
5)更大的高速缓存。由于处理器和存储器的速度差距越来越大(见1.3节),小型、快速的片上存储器用来暂存数据副本,这些刚使用过的数据或者位置靠近它们的数据可能很快被再次使用。增大高速缓存的大小并不影响应用程序性能,但是仍然需要一个折中,因为大型高速缓存比小型的速度要慢。
6)精简指令集。在20世纪80年代,指令集从CISC(Complex Instruction Set Computer,复杂指令集)转变到RISC(Reduced Instruction Set Computer,精简指令集)。在CISC中,处理器执行的指令非常复杂、功能强大,需要大型硬件对指令解码,使程序简短精湛。这减轻了程序员的负担,同时节约了稀缺的内存资源。RISC的特征是拥有一组非常简单的指令集,能够使程序的执行非常迅速(每条指令只要几个时钟周期,在极端情况下能到达每条指令仅需一个时钟周期)。RISC微处理器频率的增长速率要远远超过CISC的增长速率,此外它能够节省出更多晶体管以做他用。如今大部分用于科学计算的计算机系统在低层都使用RISC。而基于x86的处理器虽然执行CISC的机器代码,但它们却在内部将其转化为RISC模式执行。
尽管有这么多创新,但最近处理器厂商一直面临着很大障碍,很难再将单核CPU的极限性能推向一个新的层次。摩尔定律揭示了晶体管数量的稳定增长,但是更加复杂并不等价于更加有效。相反,越多的功能单元被设计到CPU中,代码不使用它们的可能性也就越大。因为在一个指令流中,独立指令的数量是有限的。此外,根据摩尔定律,时钟频率的稳定增长需要与单核性能保持同步,然而更快的时钟会增加功耗,从而使得空转晶体管更加无用。
在探索这个性能瓶颈解决方法时,人们尝试过通过放弃一些系统结构复杂性以更加直接的思路来简化处理器的设计。使用额外的晶体管以获得更大的高速缓存是思路之一,但是同样存在着局限性,更大的高速缓存不会对性能有很大的提升。多核处理器,即在单个晶片或插槽上放置多个CPU核,是如今大部分制造厂商选择的解决办法。1.4节将对其做详细讲解。
1.2.3 流水线
微处理器流水线与生产装配线有着异曲同工之妙。工人(功能单元)不需要知道最终产品的所有细节,就能够熟练、专业地完成一个单一的任务。对于不同的产品,每个工人一遍又一遍地执行相同的工作,再将半成品交给生产线上的下一个工人。如果完成一个产品需要m个不同的步骤,则会有m个不同的产品同时出现在生产线的不同阶段。如果生产线上所有阶段花费的时间相同,则所有的工人会一直处于繁忙状态。最终在每个阶段的时间内将会有一个成品产生。
诸如存取数据或浮点数运算这种复杂操作,如果没有额外的硬件支持无法在单个时间周期内完成。幸运的是,装配线的概念在这里同样适用。最简单的流水线是“取指令–分析指令–执行”,这样每个阶段可以和其他阶段独立地分开。当执行一条指令时,正在分析另一条指令,而第三条指令正在从指令高速缓存(L1I)中取出。通常这些仍然复杂的任务会被进一步细分。由于功能单元变得简单,细分任务可以获得更高的时钟速率。以浮点数运算为例,如图1-5所示,它被分解成5个简单的子任务。一个向量积A(:) = B(:)*C(:),从第一阶段开始执行,对元素B(1)和C(1)分离尾数和指数。这时剩下的四个功能单元是空闲的。于是,中间结果被交给第二阶段,而第一阶段开始处理B(2)和C(2)。在第二个周期中,有3/5的功能单元仍然是空闲的。在第四个时钟周期之后,流水线完成了它所谓的“排空”阶段。换句话说,多级流水线有一个五个时钟周期的延迟(或深度),过了这个时间,会产生第一个运算结果。然后,所有的功能单元会一直处于工作状态,每个时钟周期将会产生一个结果。因此,我们得到了每周期一条指令的吞吐量。当流水线的第一阶段完成了B(N)和C(N)的操作时,“排空”阶段开始了。四个周期之后循环结束,得到了所有的结果。


42c0456e5cf05cd275cdbd3a2c823226a3e11d66

一般情况下,对于m级流水线,执行N个独立的连续操作需要N + m - 1步。因此,对于一个需要m个时钟周期产生一个结果的通用单元,我们可以预算出其加速比,


7e004ef0d5166dc90ba450bf7a778a888b46c19c

对于很大的N,其值趋近m,则吞吐量是:


11aab768a58fc33e16ef811af528c5ade560327d

由于N很大,其结果接近于1(见图1-6)。显然,由于排空阶段的开销,为了达到合理的吞吐量,更深的流水线意味着独立操作的数量应该更大。


<a href=https://yqfile.alicdn.com/245cfafb16e59e745f012587352b76852e192420.png" >

容易确定,为了得到至少每周期p的吞吐量需要多大的N(0 < p ≤ 1):


<a href=https://yqfile.alicdn.com/1f1d09969f7c8b2ba6c4af732f6b6166bd8023a0.png" >

当p = 0.5时,Nc = m–1。考虑到当今微处理器的整体流水线长度在10~35个阶段之间,我们能用短小紧蹙的循环很快找出代码中潜在的性能瓶颈。对于超标量或向量处理器,情况会变得更加糟糕,因为它们使用多个相同的并行流水线,使得每个流水线的循环长度变得更短。
关于流水线的另一个问题是,执行诸如FP除法或是超越函数这样的复杂运算往往有很长的延迟(平方根和除法需要几十个周期,而三角函数需要更多甚至超过100个周期),它们只有很少的流水性或是根本无法流水执行,以至于整个流水线上的指令不得不被延迟,导致了所谓的流水线气泡。避免这一现象是代码优化的主要目标,这和其他相关的流水线优化一起将在第2章谈及。
需要注意的是,尽管五级流水线对于浮点数乘法流水线并不是不可行,但执行真正的代码时会涉及更多的操作,比如加载、存储、取值、译码及指令分析等,会与运算有很多重叠部分。每个指令的操作数必须从内存取到寄存器,每个结果必须被写回内存,还需要发现所有可能的依赖关系。为了使流水线的各个阶段更加有效,编译器需要安排指令的执行顺序。这对于顺序执行体系结构至关重要,同样对于存在某些大量延迟操作的乱序执行的处理器也很关键。
正如上面提到的,一条指令只有在其操作数有效时才能够被执行。如果操作数没有被及时送到执行单元,那么所有复杂的流水线机制都没有用。以下面的一个小规模循环作为例子:


<a href=https://yqfile.alicdn.com/37e69d7c152ad9b54119d1c4ab47ec9e726ac984.png" >

从高级语言的角度来看它很简单,然而这个循环对应着RISC处理器的大量汇编指令。对应的伪代码如下:

5a89cb4f8cf30840a44bc1dbae56939ef6889500

尽管乘法运算可以设计成流水线,但是如果A?(i)上的加载操作不及时提供数据,流水线会被延迟。同样,只有在乘法操作被执行以及得到有效结果后,数据存储操作才能够被执行。假设数据加载操作需要4个时钟周期,乘法运算和数据存储均需要2个时钟周期,那么很明显,上面的伪代码是很低效的。这就需要插入不相关的循环指令来避免流水线的延迟和阻塞。
当然,代码中会有排空时间,在这里没有展示。我们简单地假设CPU能够在单个时钟内发出一个循环中的所有四条指令,并且循环变量的增加和最后一个分支操作没有任何开销。交错循环的顺序来弥补时间延迟称为软流水。这种优化需要熟悉处理器体系结构和应用程序代码的编译原理。通常为达到“最佳”代码,会应用启发式算法。
但是,并不能总是通过软流水优化指令序列。由于循环中数据依赖关系的存在(例如一个循环依赖于另一个循环的结果),编译器和处理器硬件都不能避免流水线的阻塞。对于前面例子中的小规模循环,为了计算A(i)需要A(i+offset),而offset是一个编译时已知的常数或者变量。


1a2486e765496cb75b48d597d1a7fa8e86d8bf7a

偏移量作为从小到大遍历循环的索引,是负数还是正数有着很大的区别。为正数时,我们认为有一个伪相关,因为流水线计算A(i)时需要的A(i+1)总是可以获得的,则没有阻塞。然而,真相关时,流水线在计算A(i)时必须阻塞直到A(i-1)得到结果。这可能导致如图1-7中左图所示的一个很大的性能下降。该图显示了不同循环长度下浮点数运算的性能。由于片上高速缓存(cache)延时小和带宽高的特点,这种下降只在高速缓存中很明显。如果循环长度足够大使所有数据不得不从内存中取得,那么流水线阻塞的影响就微不足道了,因为这些额外的时钟周期很容易被内核等待片外数据的时钟隐藏。

<a href=https://yqfile.alicdn.com/d756ed05fe897445c0c1feb268db8dc49da85ec2.png" >

尽管可能有人认为,在编译时是否知道偏移量无关紧要,但图1-7中的右图显示出了一个可变的偏移量导致的性能戏剧性下降。在这种情况下,编译器不能使用最佳的软流水或循环优化。这实际上是一种常见现象,而不仅是跟软流水相关。为编译器隐藏信息对性能有着重大影响(这个特殊例子是由于编译器抑制了SIMD向量化,见1.2.4节和习题1.2及2.2)。
软流水的一些问题与高速缓存的使用有关,详见1.3.3节。
1.2.4 超标量
如果处理器被设计成每个时钟周期执行多条指令,或者更一般地说,每个时钟周期可以得到多个结果。在超标量设计的多个细节中可以反映这一目标:

  • 可以同时读取和分析多条指令(目前为3~6条指令)。
  • 能在多个(2~6个)整型运算单元执行地址和其他整数运算,这与上一点有很大的关联,因为需要同时执行代码才能向这些单元提供数据。
  • 并行运行多个浮点数流水线。通常有一个或两个加-乘运算相结合的管道执行像a = b+c*d这样的操作。
  • 高速缓存足够快能够达到每个时钟周期多个存取操作,可用的加载执行单元的数量(2~4个)反映了这一点。

超标量是并行执行的一种特殊形式,一种变形的指令级并行(Instruction Leud Parallelism,ILP)。为了充分利用超流水线,乱序执行和编译优化必须协同工作。然而即使是最先进的体系结构,通过编译生成的代码要达到每周期2~3条指令的吞吐量也是极其困难的。这就是为什么一些性能要求高的应用程序有时使用汇编语言编写。
1.2.5 SIMD
随着20世纪70年代第一台向量超级计算机问世,SIMD的概念变得家喻户晓(见1.6节),并且成为20世纪80年代和90年代早期大型并行互连机的基本设计原则。
最近许多基于高速缓存的处理器拥有整型和浮点型SIMD操作的扩展指令集[C107],这使人联想到它们的历史根源,那时只能执行小规模运算。它们利用一个能够装下两个双精度或4个单精度浮点数的宽寄存器来实现算法的并行操作。图1-8例子中有两个128位寄存器,每个都能存放4个单精度浮点型数据,单条指令即可一次启动4次加法运算。注意,


<a href=https://yqfile.alicdn.com/7bc32d5c7dd8d2b10be50733ef3bd7fd9f68cae4.png" >

SIMD不会关注这些操作的相关性,如果有足够的算术单元可用,这4个加法运算就能够真正并行执行,否则被送到单个流水线中。当被送到单个流水线时,SIMD会减少超标量(和复杂性)但是不会减少峰值运算性能,而当有足够的算术单元可用时,SIMD操作能较大提升峰值性能。在两种情况下,存储于系统(或至少是高速缓存)必须能够有足够大的带宽使得所有单元能够一直处于工作状态。SIMD指令集的编程和优化详见2.3.3节。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享: