新的体系结构.docx
- 文档编号:26814034
- 上传时间:2023-06-22
- 格式:DOCX
- 页数:20
- 大小:261.81KB
新的体系结构.docx
《新的体系结构.docx》由会员分享,可在线阅读,更多相关《新的体系结构.docx(20页珍藏版)》请在冰豆网上搜索。
新的体系结构
新的体系结构
在台式计算机的微处理器设计方面的最新进展包括将多个处理器放置到一个计算机芯片上。
这些多核设计完全取代了作为台式计算机的基础的单核设计。
IBM、Sun、Intel和AMD都已经将他们的芯片流水线从生产单核处理器变为生产多核处理器。
这已经促使计算机供应商将他们的重心转移到销售拥有多核的台式计算机,如Dell、HP和Apple。
在这个新的领域的市场份额的竞争中,每个计算机芯片生产商都在挑战着能够经济地放置到一个芯片上的内核数目的极限。
所有这些竞争使得消费者拥有了比以前更多的计算能力。
主要的问题在于常规的台式计算机软件没有被设计为利用新的多核架构。
实际上,为了能够从新的多核体系结构中得到任何真正的加速,将必须重新设计台式计算机软件。
设计和实现利用多核处理器的应用软件的技术,和在单核开发中使用的技术有着根本的区别。
软件设计和开发的焦点将必须从顺序编程技术转变为并行和多线程编程技术。
现在,一般开发人员的工作站以及入门级的服务器都采用具有硬件级多线程、多处理和并行处理能力的多处理器。
尽管顺序编程和单核应用开发仍将保留有一席之地,但是多核应用程序设计和开发的思想现在已经成为主流。
本章将会带您了解多核编程,介绍的内容有:
●什么是多核?
●有哪些多核体系结构以及它们之间有什么区别?
●作为软件的设计人员和开发人员,当从顺序编程和单核应用开发转移到多核编程时,需要知道哪些知识?
1.1什么是多核
多核是一种将多个处理器放置到一个计算机芯片上的架构设计。
每个处理器被称作一个核。
随着芯片容量的增加,在一个芯片上放置多个处理器变得可行。
这些设计被称为芯片多处理器(ChipMultiprocessor,CMP),是因为它们允许单芯片进行多处理。
多核是CMP或单芯片多处理器的流行的名字。
单芯片多处理的概念并不是新的,早在20世纪90年代初期,芯片生产商就已经开始探索在单芯片上放置多个内核的想法。
最近,CMP已经成为改进总体系统性能的首选方法。
这种方法与通过增加时钟频率或处理器速度来获得总体系统性能收益是截然不同的。
增加时钟频率的方法已经开始在成本效益方面达到其极限了。
较高的频率要求更多的能耗,使得系统制冷变得困难且代价高昂。
这还会影响确定尺寸和封装(sizingandpackaging)方面的考虑。
所以,现在的做法是增加更多的处理器,而不是令处理器运行得更快以获得性能上的提升。
由于这种方法实现简单,因此是更好的方法,所以该方法驱动了多核革命。
当前,多核体系结构在增强总体系统性能方面成为了焦点。
对于熟悉多处理的软件开发人员,对多核开发应当也不陌生。
从逻辑的观点,对分开封装的多个处理器进行编程与对包含在单独芯片上的单个封装上的多个处理器进行编程,两者之间并没有很大的区别。
当然可能会存在性能差异,因为新的CMP利用了总线体系结构以及处理器之间连接等方面的进步。
在某些环境下,这可能会使得原本为多个处理器编写的程序能够在CMP上更快地执行。
除了潜在的性能收益,设计和实现都是非常类似的。
在本书中,我们将讨论其中存在的微小差别。
对于只熟悉顺序编程和单核开发的开发人员,多核方法提供了很多新的软件开发范型。
1.2多核体系结构
CMP有多种形式:
两个处理器(双核)、四个处理器(四核)和八个处理器(八核)结构。
有些结构是多线程,有些结构不是。
在新的CMP中,高速缓冲存储器(cache)和内存的处理方式有着几种变体,在不同的实现中,处理器与处理器之间的通信方法也不同。
来自各大主要芯片生产商的CMP实现中,在处理I/O总线和前端总线(FrontSideBus,FSB)上均不相同。
如果严格地从逻辑视图上查看被设计为利用多核体系结构的应用程序时,并看不出大多数区别。
图1-1示范了支持多处理的3种常见配置。
图1-1
●图1-1中的配置1使用超线程(hyperthreading)。
与CMP类似,使用超线程的处理器允许在一个芯片上执行两个或多个线程。
然而,在超线程的封装中,多个处理器指的是逻辑上的,而不是物理上的。
这些封装中有着两套硬件,但是不足以构成一个单独的物理处理器。
这样,超线程允许处理器在将自身呈现给操作系统时,好像是完备的多处理器,但实际上只是在一个处理器上运行多个线程。
●图1-1中的配置2是经典的多处理器。
在配置2中,每个处理器位于一个独立的芯片上,而且有着自己的硬件。
●配置3代表了当前多处理器的发展趋势,它在一个芯片上提供完整的多个处理器。
如同您将在第2章所看到的,一些多核设计在核的内部支持超线程。
例如,一个使用了超线程技术的双核处理器可以将自己作为四核处理器呈现给操作系统。
混合型多核体系结构
混合型多核体系结构(hybridmulticorearchitecture)在一个封装中混合了多种处理器类型和/或线程模式。
这样能够通过将多种独特的性能合并到一个功能内核中,提供有效的代码优化和特化(specialization)的方法。
混合型多核架构的最常见示例是IBM的Cellbroadbandengine(Cell)。
我们将在下一章中介绍Cell的体系结构。
需要注意的是每种配置是作为由两个或更多个能够并发执行多个任务的一组逻辑处理器来呈现给开发人员的。
对系统程序员、内核程序员和应用程序开发人员的挑战是需要了解何时以及如何利用这一点。
1.3软件开发人员眼中的多核体系结构
CMP的低成本和广泛可用性,使得一般的软件开发人员能够进行各种级别的并行处理。
并行处理不再是超级计算机或集群的专属领域。
基本的开发工作站和入门级服务器现在都具有软件级和硬件级的并行处理能力。
这意味着程序员和软件开发人员可以无需牺牲设计或性能,即可根据需要部署利用多处理和多线程的应用。
然而,需要注意的是,并非每个软件应用都需要多处理或多线程。
实际上,一些软件解决方案和计算机算法最好使用顺序编程技术来实现。
在某些情况下,在软件中引入并行编程技术的开销会使软件性能降级。
并行性和多处理是需要一定成本的。
如果软件中顺序地解决问题需要的工作量少于创建额外线程和进程的开销,或者少于协调并发执行的任务之间通信的工作,则应选择顺序的方法。
有时可以较容易地确定何时及何地应当使用并行性,因为软件解决方案本身可能会要求支持并行性。
例如在很多客户端—服务器配置中,很显然是需要并行性的。
可能有一个服务器,例如数据库,还有很多可以同时对数据库发起请求的客户端。
在多数情况下,您不希望一个客户端被要求等待,直到另外一个客户端的请求被满足。
可接受的解决方案允许软件并发地处理客户端的请求。
另一方面,有时候可能会在不需要并行性时面对并行性的诱惑。
例如,您可能会倾向于相信在文本中进行并行关键字搜索理所当然地比顺序搜索快,但是这依赖于需要搜索的文本的规模,同时还依赖于启动多个并行搜索agent所需要的时间和开销数量。
设计决策者若赞成使用并发的解决方案,则必须考虑盈亏临界点和问题规模。
在多数情况下,软件设计和软件实现是分开进行的,而且很多时候是由不同的小组来执行的。
但是当主要的系统需求是软件加速或性能优化时,软件设计小组必须至少清楚软件实现的选择,而软件实现选择必须知道潜在的目标平台。
在本书中,目标平台是多核平台。
为了充分利用多核平台,您需要理解做些什么工作才能获得CMP的性能。
您需要理解CMP中的哪些部分是可以控制的。
您将看到可以通过编译器、操作系统调用/库、语言特性、应用程序级库来访问CMP。
但首先,为了理解如何处理CMP访问,需要对处理器体系结构有基本的理解。
1.3.1基本的处理器体系结构
您可以访问和影响的部件包括寄存器、主存储器、虚拟内存、指令集使用以及目标代码优化。
在试图与多处理器体系结构打交道之前,理解在单处理器架构中可以影响哪些部件非常重要。
图1-2给出了简化的处理器架构和内存部件的逻辑概览。
图1-2
处理器架构有很多变体,图1-2只是一个逻辑概览。
它说明了您可以使用的主要处理器部件。
尽管这个级别的细节和这些部件对特定类型的应用程序开发经常是透明的,但是它们在自底向上多核编程和以加速和性能优化为主要目的的软件开发中都发挥着核心作用。
与处理器的主要接口是编译器。
操作系统是二级接口。
注意:
在本书中,我们将使用C++编译器来生成目标代码。
并行编程可用于使用多种方法的所有类型的应用程序,从低级到高级,从面向对象到结构化应用程序。
C++支持多范型编程方法,因为其拥有较强灵活性,所以我们会选择使用它。
表1-1给出了编译器与CPU和指令集交互的类别清单。
包括浮点类别、寄存器操纵类别和内存模型类别。
表1-1
编译器开关选项
描述
使用的示例
Vectorization
这个选项将激活vectorizer,它是编译器中的一个组件,自动在MMX寄存器中使用单指令多数据(SingleInstructionMultipleData,SIMD)指令以及所有SSE指令集
-x-ax
激活vectorizer
Autoparallelization
这个选项识别包含并行性的循环结构,然后(如果可能)安全生成并行执行的多线程等价体
-parallel
触发自动并行化
ParallelizationwithOpenMP
使用这个选项,编译器基于程序员在源码中加入的OpenMP指示来生成多线程代码
#pragmaompparallel
{
#pragmaompfor
//yourcode
}
Fast
这个选项用于检测不兼容的处理器,在执行中生成错误消息
-O1
为代码规模和代码局部性进行优化,并禁用循环展开、软件流水和全局代码调度
-O2
默认值,将软件流水置为ON
Floatingpoint
允许编译器影响对浮点指令的选择和使用的一组开关
-fschedule-insns
告诉编译器可以发送其他指令,直到要求一个浮点指令的结果为止
-float-store
告诉编译器在生成目标代码时不使用在寄存器中存放浮点变量的指令
(续表)
编译器开关选项
描述
使用实例
Loopunrolling
这个选项用于激活循环展开;它只应用于编译器被确定为应当被展开的循环;如果将n省略,则由编译器决定是否进行展开
-unroll
激活循环展开,
n=0
禁用循环展开,仅为64位架构下的容许值
Memorybandwidth
这个选项用于激活或禁用对处理器使用的内存带宽的控制;如果禁用,则带宽会在多个线程间完全共享;可以和autoparallelization选项一同使用;这个选项仅用于64位架构
-opt-mem-bandwidth
n=2
为并行代码(如pthreads和MPI代码)激活编译器优化
n=1
为编译器生成的多线程代码激活编译器优化
Codegeneration
使用这个选项代码,为特定架构或处理器进行代码优化;如果有性能收益,编译器生成多条、处理器特定的代码路径;用于32位及64位架构
-ax
为指定处理器生成优化代码
-axS
使用SIMDExtensions4(SSE4)向量编译器和媒体加速器指令生成专门的代码路径
Threadchecking
这个选项激活使用线程的应用程序或程序中的线程分析,只能和Intel的ThreadChecker工具一同使用
-tcheck
激活使用线程的应用程序或程序的分析
Threadlibrary
这个选项使得编译器包含来自ThreadLibrary的代码;程序员需要在源代码中包含API调用
-pthread
针对多线程支持使用pthread库
1.3.2CPU(指令集)
CPU有着它识别并执行的原生指令集(nativeinstructionset)。
C++编译器的工作是将C++程序代码转换到目标平台的原生指令集。
编译器对C++进行转换并生成一个由目标处理器的原生指令组成的目标文件。
图1-3显示了基本编译过程的缩略图。
图1-3
在将C++代码转换为目标CPU本地语言的过程中,编译器可以选择如何生成目标代码。
编译器可用来帮助确定寄存器如何使用或是否执行循环展开。
可以通过对编译器选项的设置来决定是否生成16位、32位或64位目标代码。
编译器可用于选择内存模型。
编译器可以提供代码提示来声明提供多少L1(level1)cache或L2(level2)cache。
注意在表1-1中的浮点操作类别中,该类别中的开关允许编译器影响对浮点指令的选择。
例如,GNUgcc编译器有--float-store开关。
这个开关告诉编译器在生成目标代码时,不应当使用将在寄存器中存储浮点变量的指令。
SunC++编译器有-fma开关,这个开关允许自动生成浮点和multi-add指令。
-fma=none禁用了这些指令的生成。
-fma=fused开关允许编译器通过使用浮点、fused和multiply=add指令来尝试改进代码性能。
在上述两种情况下,开关都作为选项提供给编译器:
gcc–ffloat-storemy_program.cc
或
CC–fma=usedmy_program.cc
其他开关影响cache使用。
例如SunC++编译器有-xcache=c,定义了被优化器使用的cache属性。
GNUgcc编译器有-Funroll-loops,指定循环如何展开。
GNUgcc编译器的-pthread开关开启了对使用pthread的多线程的支持。
编译器甚至还有选项可用来设置典型内存引用间隔,使用的是-mmemory-latency=time开关。
实际上,对于图1-2中的任何部件,均有编译器选项和开关可影响它们的使用。
编译器提供对处理器的访问,对为特定目标处理器或处理器系列编写多核应用程序的开发人员是有影响的。
例如,UltraSparc、Opteron、IntelCore2Duo和Cell处理器都是常用的多核配置。
这些处理器均支持高速向量操作和计算。
它们支持并行计算的单指令多数据(SIMD)模型。
这种支持可以通过编译器来获得,并受编译器影响。
注意:
第4章包含了对编译器在多核开发中的作用的详细介绍。
值得注意的是,使用过多这种类型的编译器选项,会导致编译器为特定处理器进行代码优化。
如果设计目标之一是跨平台兼容,那么必须小心使用编译器选项。
对于系统程序员、库制作人员、编译器编写人员、内核开发人员、数据库和服务器引擎开发人员,对基本处理器架构、指令集和编译器接口的基本理解是开发利用CMP的有效软件的先决条件。
1.3.3内存是关键
事实上,在计算机系统中发生的所有事情都是要经过某种内存的。
多数事情需要经过很多的内存级别。
软件及同它关联的数据在执行之前通常存储在某些外部介质中(通常为硬盘、CD-ROM、DVD等)。
例如,假定您有一个非常重要且非常长的数字列表存放在一个光盘中,而且需要将这些数加到一起。
同时假定用来对这个非常长的数字列表进行累加的程序也保存在光盘中。
图1-4说明了程序和数据如何流向处理器。
图1-4
在不同类型的内存中,您必须记住的是典型的CPU仅对保存在其寄存器中的数据进行操作,它没有直接访问存储在其他位置的数据或程序的能力。
图1-4显示了ALU对寄存器的读取和写入,这是正常的状况。
指令集命令(处理器的本地语言)被设计为主要针对CPU寄存器中的数据或指令进行工作。
为了将您的重要的数字列表和程序取到处理器,必须从光盘中提取软件和数据并加载到主存储器中。
从主存储器开始,软件和数据被传递到L2cache,然后到L1cache,接下来传递到指令和数据寄存器,这样CPU可以进行它的工作。
值得注意的是在每个阶段,存储器的执行速度是不同的。
二级存储器,如CD-ROM、DVD和硬盘的速度要低于主随机存取内存(randomaccessmemory,RAM)。
RAM的速度慢于L2cache内存。
L2cache内存慢于L1cache内存。
处理器中的寄存器是您能够直接打交道的速度最快的存储器。
各种类型的存储器除了在速度方面有区别外,规模也是一个因素。
图1-5给出了分级存储器体系(memoryhierarchy)的概览。
图1-5
寄存器的速度最快,但是容量最小。
例如,一台64位计算机通常有一组寄存器,每个寄存器能够保存最多64比特。
在某些实例中,寄存器可以成对使用,允许保存128比特。
在容量方面,紧随寄存器之后的是L1cache和L2cache。
L2cache目前是以MB为单位进行度量的。
从L2cache到系统主存,在最大容量方面有一个巨大的提升,系统主存的容量当前是以GB为单位进行度量的。
除了各种类型的存储器的速度以及容量之外,还有一个因素是各类存储器之间的连接。
这些连接被证明对总体系统性能有着重要的影响。
在二级存储器中保存的数据和指令必须通过I/O通道或总线才能到达RAM。
一旦到达RAM,数据或指令通常经由系统总线到达L1cache。
I/O总线以及系统总线的速度和容量在多处理器环境中可能会成为瓶颈。
随着芯片上内核的数目的增加,总线架构以及数据通路的性能带来的影响就更加明显。
本章稍后部分将讨论总线连接,首先我们来了解分级存储器体系以及它在多核应用程序开发中扮演的角色。
要记住,就像您可以使用编译器对指令集选择的影响那样,您也可以使用编译器来操纵寄存器使用和RAM对象布局、提供cache规模提示,等等。
您可以使用更多的C++语言元素来指定寄存器使用、RAM和I/O。
这样,在您详细了解多处理和多线程之前,必须对处理器处理的分级存储器体系有基本的了解。
1.3.4寄存器
寄存器是用于特殊目的的、规模小但速度快的存储器,可以被内核直接存取。
寄存器是易失的(volatile)。
当程序退出时,程序在寄存器中用于任何意图和目的的任何数据或指令都会消失。
与交换内存、虚拟内存不同,这些内存是持久的,因为保存在某种二级存储器中,而寄存器是暂时的。
寄存器中的数据只在系统加电或程序运行期间保持。
在通用计算机中,寄存器位于处理器内部,因为几乎为零延迟。
表1-2包含了多数通用处理器中的寄存器的常见类型。
表1-2
寄存器
描述
Index
在通用计算中以及处理地址的特殊用法中使用
Segment
用于保存地址的段部分
IP
用于保存下一条要执行的指令的地址偏移部分
Counter
用于循环结构,但也可用于通用计算用途
Base
用于地址的计算和存放
Data
用作通用寄存器,而且可用于临时存储和计算
Flag
显示计算机状态或处理器状态
Floatingpoint
用于计算和移动浮点数
多数C/C++编译器有着可以影响寄存器使用的开关。
除了能够用于影响寄存器使用的编译器选项外,C++还有asm{}指示符,它允许在C++的过程或函数中写入汇编语言,例如:
voidmy_fast_calculation(void)
{
...
asm{
...
mov2,%r3
inc(%r3)
...
}
...
}
my_fast_calculation()将2加载到UltraSparc处理器的%r3通用寄存器中。
尽管对于C++,cache不是轻易可见的,但是寄存器和RAM是可见的。
根据开发的多处理器软件的类型,无论是通过编译器还是通过C++asm{}指示符,对寄存器的操纵是必要的。
1.3.5cache
cache是位于处理器和主系统内存(RAM)之间的存储器。
尽管cache不像寄存器那样快,但是仍要快于RAM。
它的容量大于寄存器,但是小于主存。
cache增加了有效的存储器传递速度,因此提高了处理器总体性能。
cache用于保存处理器最近使用的数据或指令的副本。
会从主存中获取小的内存块并保存到cache中,期望处理器会需要它们。
程序具有时间局部性(temporallocality)和空间局部性(spatiallocality)的倾向。
●时间局部性是重用最近访问的指令或数据的倾向。
●空间局部性是访问物理上接近于最近访问项的指令或数据的倾向。
cache的一项主要功能是利用程序的这种空间局部性和时间局部性的特性。
cache通常会被分为两个级别,即level1和level2。
注意:
cache的完整讨论超出了本书的范围。
若需要了解关于cache的详细讨论,可参考[Hennessy,Patterson,2007]。
1.level1cache
level1cache的规模较小,有时候只有16KB。
L1cache通常位于处理器内部,用于截获最近使用的指令或数据的字节。
2.level2cache
同L1cache相比,level2cache要大一些,但速度要慢些。
当前,它位于主板上(处理器外部),但是这一点正在逐渐变化。
当前,L2cache通常以MB为度量单位。
L2cache可以保存更大块的最近使用的指令、数据和邻近L1cache保存内容的项。
由于L1和L2快于通用RAM,因此对程序接下来要做的事情猜测得越正确,则总体系统性能越好,因为正确的数据块将位于L1cache或L2cache中。
这样就可以避免对RAM或虚拟内存乃至最坏情况下对外部存储器的访问。
3.用于cache的编译器开关
除非是在进行内核开发、编译器开发或其他类型的底层系统编程,否则多数进行多核应用程序开发的人员不会关心手工管理cache。
然而,在当前使用的多数主流编译器中,的确给出了编译器选项来提示可用的L1cache或L2cache的数量,或提示关于L1cache或L2cache的特性。
例如,SunC++编译器具有xcache开关。
该开关的参考指南显示了它的语法和用途。
-xcache=c定义了优化器可以使用的cache属性。
它并不保证使用任何特定cache属性。
尽管这个选项可以单独使用,但它通常会是-xtarget选项的扩展部分,它的主要用途是用来覆盖-xtarget选项提供的一个值。
-xcache=16/32/4:
1024/32/1指定了如下内容:
level1cache有:
level2cache有:
16KB
32B通路大小
4路相联
1024KB
32B通路大小
直接映射
开发真正利用CMP的软件要求对目标处理器或处理器系列的指令集以及内存使用进行详细的考虑。
这包括了解优化的机会,例如循环展开、高速向量处理、SIMD处理、MP编译器指示符和为某些值提供编译器提示,如L1cache或L2cache的大小。
1.3.6主存
图1-2显示了寄存器、cache、ALU和主存之间的相对关系。
除了外部存储器(例如硬盘、CD-ROM、DVD等)以外,RAM是开发人员在工作时面对的最慢的内存。
同时RAM物理上位于处理器的外部,数据通过总线传送到处理器,使得其速度更慢。
另一方面,RAM是对于多线程或多处理应用程序的软件开发人员而言最明显的部分。
多数情况下处理器和任务间共享的数据保存在RAM中,每个处理器必须执行的指令在运行时将保存在RAM中,必须在多个处理器间同步的临界区(criticalsection)也主要保存在RAM中。
如果有任务或处理器被锁住,通常是因为内存管理问题。
几乎在任何情况下,处理器和任务或多个agent之间的通信,将通过在运行时驻留在RAM中的变量、消息队列、容器和互斥量等产生。
在软件开发者的多核应用程序编程视图中,内存访问和管理是一个重要的元素。
如同已经讨论过的图1-2中显示的其他逻辑部件那样,您有权使用影响应用程序如何处理内存的编译器开关。
您所选择的内存模型非常重要。
在C++中通过new()操作符创建的对象会位于空闲存储区(堆)或虚拟内存(如果数
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 体系结构