您的位置:首页 > 其它

CPU 是如何运作的

2013-01-24 16:04 225 查看
1.3 CPU 是如何运作的(1)

上一节中我们介绍了程序经过编译器和汇编程序的转换后,最终得到机器语言的过程,也探讨了由C 语言程序生成汇编语言的过程。下面我们再进一步探究CPU 是如何执行机器语言程序的。

指令集架构与微架构

CPU 能够执行什么样的指令,或者说CPU 所具备的指令集,称为CPU的指令集架构。

指令集架构是规定程序设计如何使用指令的规范,它包括寻址模式和寄存器构成、中断、异常处理等。所以,只要指令集架构是相同的,即使使用不同品牌的CPU,所得出的结果也是一样的。比如,在上一节所展示的汇编语言程序中,只要是x86_64 架构的CPU,不管其厂家是AMD 还是英特尔,结果都是一样的。

相反,如果指令集架构不同,那么CPU 的指令也会相应发生变化。但是,不管使用的方法是否一样,几乎所有的CPU 中都具备了以下基本指令。

算术运算

逻辑运算(AND,OR,XOR)

移位指令

条件比较指令

寄存器与内存之间的传送

跳转指令

另一方面,CPU 执行指令集架构的方法叫做微架构。各品牌为了使CPU 更高效化,在设计上花费了不少功夫,所以即使是拥有同一指令集架构的CPU,其性能和负荷方面的特点也是有所不同的。

高效编程的一个重要手段就是掌握CPU 高效执行指令的方法。下面我们了解一下CPU 的基本构造,以及它是如何执行指令的。

如何执行指令

所谓计算机,就是指按顺序执行分配到内存上的程序(指令的排列),并通过这一操作完成数据处理的机器。CPU 大致按以下顺序执行指令(图1-3)。

1. 从内存读取指令(读取)

2. 解析所读取的指令(解码)

3. 提取操作目标的数据

4. 执行计算和条件对比等指令

5. 输出加工后的数据


图1-3 指令的执行
第三步是从寄存器或者内存中读取出第四步执行指令时所需要的数据。

CPU 中有分管上述各功能的管理单元,解码负责支配执行指令时各单元的运行。第四步中的指令执行装置包含执行加减运算的算术逻辑单元(ALU)、逻辑运算器、乘法器、除法器、装载/ 记忆装置等单元,与所执行的指令结合使用。如下例,图1-4 是在执行寄存器间的计算指令时各单元的运作情况,图1-5 是在执行将内存数据装载到寄存器时各单元的运作情况。



图1-4 指令的执行(寄存器间的运算)


(点击查看大图)图1-5 指令的执行(内存中数据装载到寄存器上)
1.3 CPU 是如何运作的(2)

想必读者在购买计算机的时候一定会参照CPU 的主频来确定其性能。例如,在本书实验中所使用的英特尔Xeon W5580,其主频为3.2GHz,1 秒钟内能经历32 亿个时钟周期。CPU 内的各单元可以通过获得时钟信号来进行同期运作,上述各单元中的读取、解码以及寄存器中的装载计算器等,会在1 个时钟周期内完成一次动作(除法运算和内存之间的操作会花费更多的时间,在这里只是简单地举例)。

所以,将两个寄存器的值相加,假定写入寄存器的指令适用于图1-4,则完成① ~ ⑤这一连串操作需要花费5 个时钟周期。那么执行两个指令岂不是要花10 个时钟周期?不,如果让各个单元都高效地运作起来,其性能是可以得到提升的。

指令流水线

假定现在,我们需要执行以下指令操作。

第一,将寄存器a 的值复制到寄存器c

第二,将寄存器b 的值与寄存器c 相加

第三,将寄存器c 的值向左移动2 位(值将变成原来的4 倍)

第四,对寄存器c 的值做加1 运算

图1-6 为各单元按顺序执行这一指令操作的过程。使用同样的CPU 单

元执行4 条指令仅需要8 个时钟周期,由此可见其执行性能得到很大的提升。


(点击查看大图)图1-6 指令流水线
像这样利用CPU 中各单元按流水作业的方式来执行指令,我们称之为流水线。如若众多指令能通过流水线形式来执行的话,CPU 的实际执行速度就能达到近乎1 个时钟周期完成1 条指令。

但实际上情况会复杂得多,比如执行乘除运算指令时需要花费数个时钟周期,而内存的读写则需要数十个乃至更长的时钟周期来完成。特别是内存读写比较频繁的时候,CPU 的性能会受到显著影响,所以我们需要引入缓存这一架构。

高速缓存

主存储器的存取速度相当缓慢,比CPU 要慢上数十倍甚至数百倍。在与CPU 所处的同一芯片内设计有高速小容量内存,它们的工作就是频繁复制使用中的主存储器,代替主存储器工作,我们将其称为高速缓存,由此可以缓和CPU 与主存储器之间速度不匹配的矛盾。

表1-1 为本书实验所使用设备的高速缓存型号及存取速度的相关内容。

表1-1 实验设备的高速缓存型号及存取速度


CPU 在执行指令时需要从内存上获取数据,此时CPU 首先会在缓存中查找相应的数据,在缓存内没有的情况下才会去读取主存储器上的数据(如图1-7)。读取的数据将被调入高速缓存中,以便下次使用。所以,从第二次存取开始就能大大缩短时间。


(点击查看大图)图1-7  高速缓存
存储器容量越大,存取所需的时间就越多。在现代计算机中采用了高速且小容量的一级缓存、比一级缓存容量稍大但存取速度稍慢的二级缓存,还有根据CPU 而定的容量更大的三级缓存。

1.3 CPU 是如何运作的(3)

深入探讨高速缓存

关于如何快速从缓存中读取所需数据,我们接下来做进一步的探讨。

高速缓存与缓存块(也可称为缓存条或者缓存行)被分区管理。块的型号因CPU 的构造有所不同,Xeon W5580 的一级缓存的型号为64 字节。主存储器与缓存,或者是上层缓存与下层缓存之间的数据传送,均由块单位来执行。也就是说,当CPU 想读取出4 字节的数据时,在缓存里没有该数据的情况下,将从外部内存复制已包含相应地址数据的块大小单位。

例如,假设我们有一个由1024 个64 字节的块构成的64KB 的缓存。CPU 在访问地址32008 数据的时候,首先确认是否有以32000 地址开始的64 字节的缓存数据,如若没有,则需从外部内存中获取。

如果能将内存中访问地址的一部分作为索引来使用的话,那么获取缓存内块地址就相对简单。与缓存相比,主存储器的容量较大,所以假设缓存容量为64KB,那么主存储器则分为64KB 大小的段,使其分别对应于缓存中的块(如图1-8、图1-9)。


(点击查看大图)图1-8 直接映射方式的缓存分配
1.3 CPU 是如何运作的(4)

缓存中每一组的缓存块数目(链数、路数)需要结合缓存的命中率来设计,在x86 系列的CPU 中一级缓存需要2~8 个缓存块,二级缓存则需要4~8 个,三级缓存需要4~16 个。

缓存块的替换算法

缓存中的数据随着程序的执行而不断更新。理想的情况是将经常访问的数据放在缓存中,但要想预测哪些数据常会被使用是比较困难的。

当缓存中没有空白块可分配时,就有必要置换出缓存中的某一块数据。我们依据各种置换策略决定置换哪一块数据,常用的置换策略如下。

LRU(Least Recently Used):最近最少使用的数据将被置换出缓存。这是基于“之前未被使用则以后也不会被使用”的考虑。因为需要记录缓存块的使用顺序,所以路数的增加就会导致缓存设计变得复杂。

PLRU(Pseudo-LRU):一段时间内未使用的缓存块作为替换候补,在需要替换时将某一候补替换出去。我们对某一时间段内使用过的缓存块进行标记,在需要置换的时候将未标记的某一缓存块清理出去,通过这一简单的方法即可实现PLRU 替换操作。

FIFO(First In First Out):置换最先放入缓存的缓存块。

RANDOM :随机置换。

超标量指令执行

如若进一步探究CPU 的高效化会发现,增加解码器与计算器等执行装置的数量,将若干指令并行执行,这也是一个可行之法。若干解码器安排各种CPU 单元的运作并促进程序的执行,通过这一操作可以实现多条指令并行执行。这个方法虽然在控制上比较复杂,但如果能操作好,则可实现1 个时钟周期内完成多条指令。灵活使用此类架构,首先执行非依赖指令,就能达到不按顺序执行也能得到相同结果的目的。

以上,我们走马观花地探讨了CPU 如何执行指令,以及采用怎样的方法来实现高效的操作。当然还有更为复杂的要素和手法将在之后的章节中进行说明。如果能充分理解这一章的内容,接下来的内容就会比较容易理解了。

SRAM 与DRAM

高速缓存的存取速度较之主存储器要快得多,这是因为它们的内存元件有所不同。高速缓存中所使用的SRAM(StaticRandom Access Memory) 是由晶体管(6~8 个晶体管构成1位)构成的逻辑电路组成的。逻辑电路的速度的确很快,但因为晶体管的使用数量较多,从而导致芯片上的容量相应减少。

另一方面,主存储器常用的DRAM(Dynamic RandomAccess Memory)则由电容器(容纳电荷的器件)构成,通过储存在电容器中的电荷多寡来表现其状态。根据电容器的充放电来进行读取时速度较慢,特别是在放电时会破坏储存状态,所以需要不断刷新已读出的数据(即重新充电)。但是,如果将电容器垂直(朝芯片较厚的方向)设计的话,每1 位相当于1 个晶体管大小,因此相同芯片面积上容量较大。

从内存读取数据,CPU 需要向内存发送数据地址和数据读取要求,然后内存收到指令后输出数据。

存储容量较大的DRAM,因为被用来表示地址的位数增加,所以需要通过高位低位两条指令来向内存读取数据,从而导致访问速度迟缓。但是对内存里连续存储的数据操作,高位地址只需传送一次,然后只传送低位地址即可读取数据。这样一来传送速度就提高了。缓存与主存储器之间的数据传送是通过32~64 字节的缓存块来执行的,因此传送速度的快慢至关重要。



图1-9 将内存访问地址的一部分作为索引使用
但是,这种方法会将像32000、97536、163072 等间隔为64KB(65536字节)的数据放在同一缓存块中。因此在有以上数据内存存取倾向的程序中,缓存块的替换会变得频繁,导致性能降低。但是如果我们准备若干个缓存块组,从检索序列号相同的缓存块中寻找空白块并进行分配,就能缓解性能降低的问题。这就是组相联映射方式(Set
Associative),常被应用到数据缓存当中(图1-10)。


图1-10 组相联映射方式的缓存分配
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: