程序员需要了解的CPU

477 阅读7分钟

CPU (Central Processing Unit) 是计算机的主要设备之一。

CPU 指令集

  • RISC 精简指令集
  • CISC 复杂指令集
差异RISCCISC
指令集简单,长度固定复杂、变长
寄存器通用,数量多专用,数量相对少
指令架构执行的指令以及执行结果存储只用寄存器读取、保存寄存器与内存混用

不同指令集体系影响系统功耗,执行效率,应用领域。

编码层面的考量,代码需简洁、高效,减少无效代码,只要是代码就必定会有消耗。

CPU 执行

冯·诺伊曼体系架构

CPU的运作原理可分为四个阶段:取指和预解码、解码、执行和写回。执行完毕,更新“指令指针”,循环往复。

取指和预解码,“指令指针”(寄存器)指向内存地址,读取指令(固定/变长);解码,依据指令集,拆解读取的指令为指令片段;执行,通过CPU内部运算器,进行指令计算;写回,指令执行结果写回寄存器或者内存。

CPU 缓存

计算机系统中,CPU高速缓存(英语:CPU Cache,在本文中简称缓存)是用于减少处理器访问内存所需平均时间的部件。在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器。其容量远小于内存,但速度却可以接近处理器的频率。

当处理器发出内存访问请求时,会先查看缓存内是否有请求数据。如果存在(命中),则不经访问内存直接返回该数据;如果不存在(失效),则要先把内存中的相应数据载入缓存,再将其返回处理器。

缓存之所以有效,主要是因为程序运行时对内存的访问呈现局部性(Locality)特征。这种局部性既包括空间局部性(Spatial Locality),也包括时间局部性(Temporal Locality)。有效利用这种局部性,缓存可以达到极高的命中率。

在处理器看来,缓存是一个透明部件。因此,程序员通常无法直接干预对缓存的操作。但是,确实可以根据缓存的特点对程序代码实施特定优化,从而更好地利用缓存。

[CPU 缓存]  zh.wikipedia.org/wiki/CPU%E7… CPU 缓存

现代 CPU 为了减少与内存交互,提升执行效率,采用多级缓存架构,常见有三级缓存架构。L1,L2 层 CPU 独享,L3 层 CPU 共享,,越接近 CPU 的缓存访问速度越快、容量越小。

CPU 缓存的最小表示单位为 Cache Line (缓存行),CPU 缓存未命中时,即会向内存发起读取,每次读取数据的单位就是 1 缓存行,这也就是空间局部性特征存在的原因。

由于空间局部性特征,编码时可以充分利用该特性,以顺序访问连续内存的形式,提高缓存命中率,减少与内存交互消耗,提升程序执行效率。

缓存回写

为了和下级存储(如内存)保持数据一致性,就必须把数据更新适时传播下去。这种传播通过回写来完成。一般有两种回写策略:写回(Write back)和写通(Write through)。

写回是指,仅当一个缓存块需要被替换回内存时,才将其内容写入内存。如果快取命中,则总是不用更新内存。为了减少内存写操作,缓存块通常还设有一个脏位(dirty bit),用以标识该块在被载入之后是否发生过更新。如果一个缓存块在被置换回内存之前从未被写入过,则可以免去回写操作。

写回的优点是节省了大量的写操作。这主要是因为,对一个数据块内不同单元的更新仅需一次写操作即可完成。这种内存带宽上的节省进一步降低了能耗,因此颇适用于嵌入式系统。

回写策略分配策略当……时写到……
写回分配命中缓存
写回分配失效缓存
写回非分配命中缓存
写回非分配失效内存
写通分配命中快取和内存
写通分配失效快取和内存
写通非分配命中快取和内存
写通非分配失效内存

写通是指,每当缓存接收到写数据指令,都直接将数据写回到内存。如果此数据地址也在缓存中,则必须同时更新缓存。由于这种设计会引发造成大量写内存操作,有必要设置一个缓冲来减少硬件冲突。这个缓冲称作写缓冲器(Write buffer),通常不超过4个快取Block大小。不过,出于同样的目的,写缓冲器也可以用于写回型缓存。

写通较写回易于实现,并且能更简单地维持数据一致性。

[CPU 缓存]  zh.wikipedia.org/wiki/CPU%E7… CPU 缓存

由于 CPU 的独享缓存,及缓存回写机制,当发生不同 CPU 读写各自缓存中加载自内存同一位置数据时,会存在缓存写回时的一致性问题。内存屏障可以解决缓存一致性问题。

内存屏障

内存屏障(英语:Memory barrier),也称内存栅栏内存栅障屏障指令等,是一类同步屏障指令,它使得 CPU 或编译器在对内存进行操作的时候, 严格按照一定的顺序来执行, 也就是说在memory barrier 之前的指令和memory barrier之后的指令不会由于系统优化等原因而导致乱序。

大多数现代计算机为了提高性能而采取乱序执行,这使得内存屏障成为必须。

语义上,内存屏障之前的所有写操作都要写入内存;内存屏障之后的读操作都可以获得同步屏障之前的写操作的结果。因此,对于敏感的程序块,写操作之后、读操作之前可以插入内存屏障。

[内存屏障]  zh.wikipedia.org/wiki/%E5%86… 内存屏障

Java volatile 关键字修饰变量时,JMM(Java Memory Model)会在写变量的指令后面,插入 write barrier 指令,并在读变量指令前插入 read barrier 指令,来实现该修饰变量的内存可见性。

多级缓存意义

缓存大小按距离 CPU 距离递增,距离 CPU 越近,期望 CPU 访问数据肯定是越快,如果缓存过大,相对而言,访问速度会降低很多。

CPU 指令重排

编译器优化也存在指令重排

CPU 计算单元的计算能力显著超过 CPU 访问缓存的速度时,会导致缓存等待。为了减少上述等待耗时,提升 CPU 计算资源利用率,CPU 架构设计时会采用一种缓存分片方案,将一块缓存划分成互不相关的多个逻辑单元,CPU 从中获取空闲单元进行操作,副作用就会导致 CPU 指令执行顺序无法保证,出现指令乱序现象,即 CPU 指令重排。

当单线程无并发场景下,CPU 指令重排能保证最终结果正确,但是当多线程并发场景下,由于 CPU 指令重排现象的存在,会导致程序异常。比如,某个并发访问变量初始化时发生指令重排,引用赋值比类实例初始化代码先执行,另一线程使用该变量时,引用已完成初始化赋值,但并未完成引用所指向类实例的初始化,那么后续通过该引用的操作可能会出错。

内存屏障同样可以解决指令重排问题。

Java volatile synchronized 关键字修饰变量都可以防止 CPU 指令重排,volatile 保障了可见性, synchronized 在可见性的基础上,还保障了原子性。


[寻址模式]  zh.wikipedia.org/wiki/%E5%AF… 寻址模式

[读取变长指令]  stackoverflow.com/questions/8… 读取变长指令