CPU指令结构
    CPU内部结构

    • 控制单元
    • 运算单元
    • 数据单元

    image.png
    控制单元
    控制单元是整个CPU的指挥控制中心,由指令寄存器IR(Instruction Register)、指令译码器ID(Instruction Decoder)和 操作控制器OC(Operation Controller) 等组成,对协调整个电脑有序工作极为重要。它根据用户预先编好的程序,依次从存储器中取出各条指令,放在指令寄存器IR中,通过指令译码(分析)确定应该进行什么操作,然后通过操作控制器OC,按确定的时序,向相应的部件发出微操作控制信号。操作控制器OC中主要包括:节拍脉冲发生器、控制矩阵、时钟脉冲发生器、复位电路和启停电路等控制逻辑。
    运算单元
    运算单元是运算器的核心。可以执行算术运算(包括加减乘数等基本运算及其附加运算)和逻辑运算(包括移位、逻辑测试或两个值比较)。相对控制单元而言,运算器接受控制单元的命令而进行动作,即运算单元所进行的全部操作都是由控制单元发出的控制信号来指挥的,所以它是执行部件。
    存储单元
    存储单元包括 CPU 片内缓存Cache和寄存器组,是 CPU 中暂时存放数据的地方,里面保存着那些等待处理的数据,或已经处理过的数据,CPU 访问寄存器所用的时间要比访问内存的时间短。 寄存器是CPU内部的元件,寄存器拥有非常高的读写速度,所以在寄存器之间的数据传送非常快。采用寄存器,可以减少 CPU 访问内存的次数,从而提高了 CPU 的工作速度。寄存器组可分为专用寄存器和通用寄存器。专用寄存器的作用是固定的,分别寄存相应的数据;而通用寄存器用途广泛并可由程序员规定其用途。
    下表列出了CPU关键技术的发展历程以及代表系列,每一个关键技术的诞生都是环环相扣的,处理器这些技术发展历程都围绕着如何不让“CPU闲下来”这一个核心目标展开。

    关键技术 时间 描述
    指令缓存(L1) 1982 预读多条指令
    数据缓存(L1) 1985 预读一定长度的数据
    流水线 1989 一条指令被拆分由多个单元协同处理, i486
    多流水线 1993 多运算单元多流水线并行处理, 奔腾1
    乱序+分支预测 1995 充分利用不同组件协同处理, 奔腾Pro
    超线程 2002 引入多组前端部件共享执行引擎, 奔腾4
    多核处理器 2006 取消超线程,降低时钟频率,改用多核心, Core酷睿
    多核超线程 2008 重新引入超线程技术,iX系列

    CPU缓存结构
    现代CPU为了提升执行效率,减少CPU与内存的交互(交互影响CPU效率),一般在CPU上集成了多级缓存架构,常见的为三级缓存结构

    • L1 Cache,分为数据缓存和指令缓存,逻辑核独占
    • L2 Cache,物理核独占,逻辑核共享
    • L3 Cache,所有物理核共享

    image.png
    存储器存储空间大小:内存>L3>L2>L1>寄存器;
    存储器速度快慢排序:寄存器>L1>L2>L3>内存;
    还有一点值得注意的是:缓存是由最小的存储区块-缓存行(cacheline)组成,缓存行大小通常为64byte。
    缓存行是什么意思呢?
    比如你的L1缓存大小是512kb,而cacheline = 64byte,那么就是L1里有512 1024/64个cacheline
    CPU读取存储器数据过程
    1、CPU要取寄存器X的值,只需要一步:直接读取。
    2、CPU要取L1 cache的某个值,需要1-3步(或者更多):把cache行锁住,把某个数据拿来,解锁,如果没锁住就慢了。
    3、CPU要取L2 cache的某个值,先要到L1 cache里取,L1当中不存在,在L2里,L2开始加锁,加锁以后,把L2里的数据复制到L1,再执行读L1的过程,上面的3步,再解锁。
    4、CPU取L3 cache的也是一样,只不过先由L3复制到L2,从L2复制到L1,从L1到CPU。
    5、CPU取内存则最复杂:通知内存控制器占用总线带宽,通知内存加锁,发起内存读请求,等待回应,回应数据保存到L3(如果没有就到L2),再从L3/2到L1,再从L1到CPU,之后解除总线锁定。
    CPU为何要有高速缓存
    CPU在摩尔定律的指导下以每18个月翻一番的速度在发展,然而内存和硬盘的发展速度远远不及CPU。这就造成了高性能能的内存和硬盘价格及其昂贵。然而CPU的高度运算需要高速的数据。为了解决这个问题,CPU厂商在CPU中内置了少量的高速缓存以解决I\O速度和CPU运算速度之间的不匹配问题。
    在CPU访问存储设备时,无论是存取数据抑或存取指令,都趋于聚集在一片连续的区域中,这就被称为局部性原理。
    时间局部性(Temporal Locality):如果一个信息项正在被访问,那么在近期它很可能还会被再次访问。
    比如循环、递归、方法的反复调用等。
    *空间局部性(Spatial Locality)
    :如果一个存储器的位置被引用,那么将来他附近的位置也会被引用。
    比如顺序执行的代码、连续创建的两个对象、数组等。
    举个空间局部性原则例子:

    1. public class TwoDimensionalArraySum {
    2. private static final int RUNS = 100;
    3. private static final int DIMENSION_1 = 1024 * 1024;
    4. private static final int DIMENSION_2 = 6;
    5. private static long[][] longs;
    6. public static void main(String[] args) throws Exception {
    7. /*
    8. * 初始化数组
    9. */
    10. longs = new long[DIMENSION_1][];
    11. for (int i = 0; i < DIMENSION_1; i++) {
    12. longs[i] = new long[DIMENSION_2];
    13. for (int j = 0; j < DIMENSION_2; j++) {
    14. longs[i][j] = 1L;
    15. }
    16. }
    17. System.out.println("Array初始化完毕....");
    18. long sum = 0L;
    19. long start = System.currentTimeMillis();
    20. for (int r = 0; r < RUNS; r++) {
    21. for (int i = 0; i < DIMENSION_1; i++) {//DIMENSION_1=1024*1024
    22. for (int j=0;j<DIMENSION_2;j++){//6
    23. sum+=longs[i][j];
    24. }
    25. }
    26. }
    27. System.out.println("spend time1:"+(System.currentTimeMillis()-start));
    28. System.out.println("sum1:"+sum);
    29. sum = 0L;
    30. start = System.currentTimeMillis();
    31. for (int r = 0; r < RUNS; r++) {
    32. for (int j=0;j<DIMENSION_2;j++) {//6
    33. for (int i = 0; i < DIMENSION_1; i++){//1024*1024
    34. sum+=longs[i][j];
    35. }
    36. }
    37. }
    38. System.out.println("spend time2:"+(System.currentTimeMillis()-start));
    39. System.out.println("sum2:"+sum);
    40. }
    41. }

    带有高速缓存的CPU执行计算的流程

    1. 程序以及数据被加载到主内存
    2. 指令和数据被加载到CPU的高速缓存
    3. CPU执行指令,把结果写到高速缓存
    4. 高速缓存中的数据写回主内存

    CPU运行安全等级
    CPU有4个运行级别,分别为:

    • ring0
    • ring1
    • ring2
    • ring3

    Linux与Windows只用到了2个级别:ring0、ring3,操作系统内部内部程序指令通常运行在ring0级别,操作系统以外的第三方程序运行在ring3级别,第三方程序如果要调用操作系统内部函数功能,由于运行安全级别不够,必须切换CPU运行状态,从ring3切换到ring0,然后执行系统函数,说到这里相信同学们明白为什么JVM创建线程,线程阻塞唤醒是重型操作了,因为CPU要切换运行状态。
    下面我大概梳理一下JVM创建线程CPU的工作过程
    step1:CPU从ring3切换ring0创建线程
    step2:创建完毕,CPU从ring0切换回ring3
    step3:线程执行JVM程序
    step4:线程执行完毕,销毁还得切会ring0
    讲完了CPU部分,我们下一章节看下内存部分。