冯诺依曼体系
目前的通用类型计算机, 都是冯诺依曼体系结构. 有以下主要特点:
- 以运算单元为中心
- 采用存储程序原理
- 存储器是按照地址访问, 线性地址空间
- 控制器由指令流产生
- 指令由操作码和地址码组成
- 数据以二进制编码
按照单元看来, 有控制器
, 运算器
, 存储器
, 输出设备
和输出设备
五个部分.
在服务器中, 输入设备
可能就是:(配置)文件, 网络(socket, 数据库), 控制台; 输出设备
可能就是: 文件, 控制台, 网络等; 存储器
一般是CPU的缓存, 内存, 磁盘等; 运算器
和控制器
一般都在CPU内部.
所以, 想要写出效率极高的程序, 是需要对计算机体系结构有一点的了解.
执行单元和流水线
现代的CPU, 对CISC和RISC区分的已经不是那么明显. CPU在执行具体指令的时候, 会将当前的指令, 转换成内部真正需要处理的微指令, 这一步叫译码. 然后可能会做读取寄存器的操作, 再做具体指令的运行, 接着做存储器的访问, 保存结果等.
这个完整的操作叫做Pipeline, 是一种非常基础模式, 用来提高可分解任务
的吞吐量;
还有一种模式叫Cluster, 一个CPU核内部, 可能会有多个相同的单元, 比如整数运算单元, 因为对整数的加减乘除运算使用的频次可能会非常高, 所以提高运算单元的数量, 可以显著提高吞吐量.
不管是Pipeline还是Cluster模式, 都是整个计算机系统里面甚至软件系统里面最基础的模式, 单独使用一种模式是不合适的, 需要搭配着使用. 选择的关键点在于任务的可分解性
.
实际上CPU在执行指令的时候, 做的pipeline比上面解释的要复杂的多, 但是原理类似.
乱序执行
某一个指令, 需要用到A地址的内存, 但是该内存并没有在CPU的缓存上, 那么该内存需要被读取到CPU的缓存上, 然后才能接着执行指令. 这个操作叫顺序执行
. 早期的CPU, 甚至前几年的ARM CPU, 都是这么实现, 一方面实现简单, 另外一方面功耗低. 但是这种CPU性能比较底, 一旦缓存未命中, CPU就需要等待, 会影响其他之类的执行.
所以高性能的CPU, 都会引入乱序执行
(out-of-order execution), 缓存如果命中, 那么就继续执行; 未命中, 就去调度, 然后将指令放入到队列中, 分析后续的指令, 哪个可以先执行而不影响程序的结果, 就调度上来. 这样可以提高CPU的实际吞吐量.
存储以及其层级结构
- 离CPU最近缓存就是寄存器, 和CPU同频, 个数受限, 延迟为1个时钟周期
- 下来就是L1, L1有两种, 指令缓冲和数据缓冲. L1数据缓冲部分, 一般大小是32K-64KB. 延迟的典型值是4-6时钟周期
- 接下来是L2, 容量较大, 典型大小是256KB, 延迟为12个时钟周期
- x64 CPU都有L3, ARM不一定都有L3. 通常L3的大小比L2大很多, 不同于L1/L2, L3一般是共享的. 大小的典型值是8MB(ARM的要小一些, 一般为1-2MB). L3的延迟一般在40个时钟周期以上.
上面是常见的CPU存储的典型值, 具体的CPU需要查看手册.
CPU存储的组织
一般是以近似于LRU
的HashTable来实现.
CPU的缓存大小都非常小, 所以需要合理的使用. CPU在使用缓存的时候, 是以CacheLine
这样的结构来使用, CPU不能单独定位一个地址, 只能把这个地址所在的CacheLine全部Load到L1里面. CacheLine的典型大小是64B, 老的CPU也有32B的, 也有一些高性能CPU是128B, 比较少见.
CPU对将某一些CacheLine映射到一簇, 比如8个CacheLine, 在这个8个CacheLine, 通过LRU算法实现剔除替代, 从而高效的利用缓存. 通常都是LRU的近似算法
. 甚至有一些CPU用FIFO算法来做CacheLine的替代.
缓存的一致性
L1/L2大多数是CPU内核独占的. 那么一个对象, 有可能会被多个CPU内核同时操作.
现代CPU都通过一种MSEI协议
或其近似协议, 来保证CacheLine的一致性.
简单的说, 就是同时只能