现代计算机系统
现代计算机系统与冯·诺依曼计算机差别不大,最大的区别冯·诺依曼计算机 是 以运算器为中心的,而现代计算机 以储存器为中心:
我们主要来看一下其中与储存相关的组件:
存储器
存储器是用来存放数据和程序。存储器 包含主存和辅存
- 主存:直接与CPU交换信息,就是我们熟悉的内存。断电后内存的数据是会丢失的
- 辅存:辅存可作为主存的后备存储器,不直接与CPU交换信息,容量比主存大,但速度比主存慢。比如机械硬盘、固态硬盘等。断电后硬盘的数据是不会丢失,硬盘是持久化存储设备。
- 辅存、输入设备、输出设备 统称为IO设备;主机一般包含:CPU、主存
我们先来看看存储器的层次结构,来初步对各个储存器部件有所认识
我们可以发现存储器速度越快的话,相应的价格也会越发昂贵!
寄存器
CPU中 还有一个常见的组件: 寄存器,是CPU内部用来存放数据的一些小型的存储区域,用来暂时存放参与运算的数据以及运算结果。寄存器由电子线路组成,存取速度非常快,寄存器的成本较高,因而数量较少。
CPU时钟周期
CPU时钟周期:通常为节拍脉冲或T周期,即主频的倒数,它是CPU中基本时间单位。平时我们打游戏常说的超频,超的就是这个CPU主频。
举个例子,主频为3.0GHZ的CPU,一个时钟周期大约是0.3纳秒,内存访问大约需要120纳秒,固态硬盘访问大约需要50-150微秒,机械硬盘访问大约需要1-10毫秒,最后网络访问最慢,得几十毫秒左右。
这个大家可能对时间不怎么敏感,那如果我们把一个时钟周期如果按1秒算的话,内存访问大约就是6分钟 ,固态硬盘大约是2-6天 ,传统硬盘大约是1-12个月,网络访问就得几年了!我们可以发现CPU的速度和内存等存储器的速度,完全不是一个量级上的。
高速缓存
为了弥补 CPU 与内存两者之间的性能差异,就在 CPU 内部引入了 CPU Cache,也称高速缓存。CPU Cache用的是 SRAM(Static Random-Access Memory)的芯片,也叫静态随机存储器。其只要有电,数据就可以保持存在,而一旦断电,数据就会丢失。
CPU Cache 通常分为大小不等的三级缓存,分别是 L1 Cache、L2 Cache 和 L3 Cache
部件 | CPU访问所需时间 | 备注 |
L1 高速缓存 | 2~4 个时钟周期 | 每个 CPU 核心都有一块属于自己的 L1 高速缓存,L1 高速缓存通常分成指令缓存和数据缓存。 |
L2 高速缓存 | 10~20 个时钟周期 | L2 高速缓存同样是每个 CPU 核心都有的 |
L3 高速缓存 | 20~60个时钟周期 | L3 高速缓存是多个 CPU 核心共用的 |
我们可以发现越靠近 CPU 核心的缓存其访问速度越快。
程序执行时,会先将内存中的数据加载到共享的 L3 Cache 中,再加载到每个核心独有的 L2 Cache,最后 进入到最快的 L1 Cache,之后才会被 CPU 读取。层级关系如下图:
主存
主存,直接与CPU交换信息,就是我们熟悉的内存。它使用的是一种叫作 DRAM(Dynamic Random Access Memory)的芯片,也叫动态随机存取存储器。断电后内存的数据是会丢失。DRAM 芯片的密度更高,功耗更低,有更大的容量,造价比 SRAM 芯片便宜很多,但速度比SRAM 芯片慢的多。
内存速度大概在 200~300 个 时钟周期之间
固态硬盘
固体硬盘(Solid-state Disk, SSD),数据直接存在闪存颗粒中,并且由主控单元记录数据存储位置和数据操作,每一个闪存颗粒的存储容量是有限的;
但是它相比内存的优点是断电后数据还是存在的,SSD固体硬盘的读写速度虽然比内存的大概慢10~1000 倍,但比机械硬盘快多了,当然价格也昂贵很多。不过随着时代的发展,固态硬盘的价格慢慢趋向接近机械硬盘。
机械硬盘
机械硬盘(_Hard Disk Drive, HDD_),它是通过物理读写的方式来访问数据的,机械硬盘在盘面上写数据、磁盘转动,机械臂移动,比较原始的数据读写方式,就像近现代的留声机发声原理一样。
由于受限于转盘转速与指针寻址的时间限制,因此它访问速度是非常慢的,它的速度比内存慢 10W 倍左右。当然机械硬盘也是有其优点的:容量大,价格便宜,恢复数据难度低,因此数据放在机械硬盘中比较保险。
压榨CPU性能带来的问题
由于CPU速度非常快,且价格非常昂贵,我们必须得充分压榨CPU,得像生产队的驴一样,让它不停地工作
为了合理利用 CPU 的高性能,同时尽可能地节约成本,现代计算机将这些储存器充分的结合起来,由于这些硬件的数据存取速度差异导致了计算机系统编程中的各种问题:
有序性问题
为了充分压榨CPU的性能,CPU 会对指令乱序执行或者语言的编译器会指令重排,让CPU一直工作不停歇,但同时会导致有序性问题。
在CPU中为了能够让指令的执行尽可能地同时运行起来,采用了指令流水线。一个 CPU 指令的执行过程可以分成 4 个阶段:取指、译码、执行、写回。这 4 个阶段分别由 4 个独立物理执行单元来完成。
理想的情况是:指令之间无依赖,可以使流水线的并行度最大化。但是如果两条指令的前后存在依赖关系,比如数据依赖,控制依赖等,此时后一条语句就必需等到前一条指令完成后,才能开始。所以CPU为了提高流水线的运行效率,对无依赖的前后指令做适当的乱序和调度。
还有一种情况编译器会指令重排,比如java语言,JVM 的编译器会对其指令进行重排序的优化(指令重排)。
所谓指令重排是指在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快。JVM中并没有规定编译器优化相关的内容,也就是说JVM可以自由的进行指令重排序的优化。
无论是编译期的指令重排还是CPU 的乱序执行,主要都是为了让 CPU 内部的指令流水线可以“填满”,提高指令执行的并行度,充分利用CPU的高性能。
可见性问题
为了平衡CPU的寄存器和内存的速度差异,计算机的CPU 增加了高速缓存,但同时导致了 可见性问题。我们知道当程序执行时,一般CPU会去从内存中读取数据,来进行计算。CPU计算完之后,需要把数据重新放回到内存中。
当CPU的多个核心参与一个程序的运行,从内存中读取一个共享变量的数据,当不同核心间进行了各自的计算,把计算后的值放入自己的缓存中而不选择立即写入内存中(CPU写入内存的时机是不确定的)。那么在CPU的缓存中,这个共享变量有可能存放着不同的数据,这就导致了缓存的可见性问题。即一个线程对数据的修改无法对其他线程可见。
原子性问题
为了平衡CPU 与 I/O 设备的速度差异,操作系统增加了进程、线程概念,以分时复用 CPU,但同时导致了原子性问题。
原子操作就是不可分割的操作,在计算机中,就是指不会因为线程调度被打断的操作。
当一个程序去I/O 设备读取数据, 由于I/O 设备数据存入读取速度,相比于CPU的执行速度来说度日如年,CPU这么牛逼这么昂贵的宝贝,怎么能让它歇着,得让它一直干活,去切换执行其他程序。也就是将CPU的时间进行分片,让各个程序在CPU上轮转执行。但被剥夺执行权的程序,等它从IO读取完数据后,还是得让CPU继续执行的,这时需要一个数据结构来保存,以便之后恢复继续执行,这个就是进程。
一开始进程中 只有一个"执行流",干活的人就一个。随着任务越来越多,发现进程不够用了,经常导致整个程序被阻塞,这时计算机让进程有多个执行流,干活的人变多了,那程序就不会再被阻塞了,"执行流" 就是线程。
如何解决这3个问题,就是并发、多线程需要处理的事,当然这是后话。
参考资料:
《深入理解计算机系统》
《计算机组成原理》
《计算机组成原理》--唐朔飞
https://zhuanlan.zhihu.com/p/379947484
本文转载自微信公众号「 小牛呼噜噜」,作者「小牛呼噜噜」,可以通过以下二维码关注。