Fork me on GitHub

深入浅出计算机组成原理——原理篇:存储与I/O系统(35-39)

全文内容主要来自对课程《深入浅出计算机组成原理》的学习笔记。

35 | 存储器层次结构全景

理解存储器的层次结构

寄存器:更像 CPU 本身的部分,空间极其有限,但速度非常快,
CPU Cache:CPU 高速缓存,我们常常简称为“缓存”,使用 SRAM 芯片。

SRAM

SRAM(Static Random-Access Memory),即静态随机存取存储器。只要处在通电状态,里面的数据就可以保持存在,一断电就丢失。

SRAM 中,1 bit 的数据,需要 6~8 个晶体管。存储密度不高,但电路简单,速度快。

CPU 有 L1、L2、L3 这样三层高速缓存
L1 Cache:每个 CPU 有自己的,在CPU内部,分成指令缓存和数据缓存;
L2 Cache:每个 CPU 有自己的,不在CPU内,速度略慢;
L3 Cache:多个 CPU 共享,尺寸大,速度更慢。

DRAM

内存和 Cache 不同,用 DRAM(Dynamic Random Access Memory,动态随机存取存储器)芯片。密度高,大容量。

DRAM 需要靠不断地“刷新”,才能保持数据被存储。1 bit只需要一个晶体管和一个电容就能存储。存储密度大,速度比 SRAM 慢。且电容会漏电,需要定时刷新充电。

存储器的层级结构

可以看到,从 Cache 到 HDD,容量越来越大,价格越来越便宜,速度越来越慢。
故,一个完整计算机,会通过不等层级的内存组合,来实现性价比:
少量贵的存储保障热数据的速度,大量便宜的存储来提供冷数据存储

并且,各个存储器只和相邻的一层存储器打交道


36 | 局部性原理

服务端开发时,数据一般存在数据库,访问数据库性能瓶颈是要点,一般就会用缓存来缓解,

理解局部性原理

挑战:既要 CPU Cache 的速度,又要内存、硬盘巨大的容量和低廉的价格。

解法便是局部性原理(Principle of Locality):时间局部性(temporal locality)和空间局部性(spatial locality)

  • 时间局部性:如果一个数据被访问了,那么它在短时间内还会被再次访问。
  • 空间局部性:如果一个数据被访问了,那么和它相邻的数据也很快会被访问。

使用缓存的时候,例如LRU(Least Recently Used)缓存算法,需要关注缓存命中率,越高说明缓存效果越好。


37 | 高速缓存(上)

需要高速缓存

基于摩尔定律,CPU 和 内存的访问速度差异越来越大,为了缓解数据跟不上计算的问题,在CPU中就引入了高速缓存。

在 95% 的情况下,CPU 都只需要访问 L1-L3 Cache,从里面读取指令和数据,而无需访问内存

CPU 从内存中读取数据到 CPU Cache ,是以小块为单位的而不是单个元素,在 CPU Cache 里面,叫作 Cache Line(缓存块),常是 64 字节(Bytes)。

Cache 的数据结构和读取过程是

CPU 读取数时:

  1. 先访问 Cache;
  2. 有,则取出;
  3. 没有,再访问内存;
  4. 并将读取的数写入到 Cache。

问题:CPU 如何知道需要访问的内存数据对应的 Cache 位置呢?
答案:直接映射 Cache(Direct Mapped Cache)。
思路:CPU 拿到的是数据所在的内存块(Block)的地址,其通过 mod 运算,固定映射到一个的 CPU Cache 地址(Cache Line),作为索引

mod 运算的技巧:缓存块的数量设置成 2 的 N 次方,直接取地址的低 N 位就是 mod 结果。

这时候,肯定会有多个内存块地址映射到同一个 Cache 地址,需要辨识当前存储的是哪一块。
缓存块中,我们会存储一个组标记(Tag),记录当前缓存块内存储的数据对应的内存块。

此外,缓存块中还有:

  • 实际存放的数据,一个 Block;
  • 有效位(valid bit),其0/1代表是否可用;
  • 偏移量(Offset),记录需要取的数据在 Block 中哪个位置。

“索引 + 有效位 + 组标记 + 数据”数据结构,使得 Cache 访问时有4步:

  1. 取内存地址低位,计算 Cache 对应的索引;
  2. 根据有效位,判断 Cache 数据是否可用;
  3. 取内存地址高位,和组标记,确认数据是否符合为目标数据,从 Cache Line 中读取到对应的数据块(Data Block);
  4. 根据内存地址的 Offset 位,从 Data Block 中,读取希望读取到的字。

如在 2、3 步骤中发现数据不可用,那 CPU 就会访问内存,并把对应的 Block Data 更新到 Cache Line 中,同时更新有效位和组标记的数据。


38 | 高速缓存(下)

volatile 关键词

作者从 java 中“volatile”关键词出发,讨论它的作用和原理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class VolatileTest {
private static volatile int COUNTER = 0;

public static void main(String[] args) {
new ChangeListener().start();
new ChangeMaker().start();
}

static class ChangeListener extends Thread {
@Override
public void run() {
int threadValue = COUNTER;
while ( threadValue < 5){
if( threadValue!= COUNTER){
System.out.println("Got Change for COUNTER : " + COUNTER + "");
threadValue= COUNTER;
}
// try {
// Thread.sleep(5);
// } catch (InterruptedException e) { e.printStackTrace();}
}
}
}

static class ChangeMaker extends Thread{
@Override
public void run() {
int threadValue = COUNTER;
while (COUNTER <5){
System.out.println("Incrementing COUNTER to : " + (threadValue+1) + "");
COUNTER = ++threadValue;
try {
Thread.sleep(500);
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
}

先说结果:

  • 直接运行,ChangeListener 能够监听 COUNTER 变化;
  • 去掉 volatile 则不行;
  • 去掉 volatile,但是又让 ChangeListener 每次 sleep 5ms 则又可以。

volatile 会确保我们对于这个变量的读取和写入,都一定会同步到主内存里,而不是从 Cache 里面读取。

写直达(Write-Through)

每一次数据都要写入到主内存里面。

  • 先判断是否在 Cache;
  • 在,先写 Cache,再写入主内存;
  • 不在,直接写主内存。

写回(Write-Back)

只写到 CPU Cache 里。只有当 CPU Cache 里面的数据要被“替换”的时候,我们才把数据写入到主内存里面去。

结合流程图,逻辑比较清晰。重点解释一下其中的“脏”的概念。
标记“脏”:就是指这个时候,CPU Cache 里面的这个 Block 的数据,和主内存是不一致的。

以上2中写法都需要考虑一个问题,就是在多线程/多CPU时缓存一致性的问题。


39 | MESI协议解决缓存一致性

缓存一致性问题

假设有2核CPU,执行改价格任务,如果核1改了价格,写入到 Cache 中,在 Cache Block 交换出去前不会写入到内存,那么核2在这期间取到的数据就不一致。

为了解决此,需要做到:

  • 写传播:Cache 的更新必须同步到其他 CPU 核的 Cache里。
  • 事务的串行化:按顺序执行修改,防止不同 CPU 核之间乱序。

总线嗅探机制和 MESI 协议

总线嗅探(Bus Snooping)

把所有的读写请求都通过总线(Bus)广播给所有的 CPU 核心,然后让各个核心去嗅探这些请求,再根据本地的情况进行响应。

写失效(Write Invalidate)协议:只有一个 CPU 核心负责写入数据,其他的核心,只是同步读取到这个写入。
写广播(Write Broadcast)协议:一个写入请求广播到所有的 CPU 核心,同时更新各个核心里的 Cache,大家一起更新。

MESI 协议对 Cache Line 有 4 种标记:

  • M:代表已修改(Modified);
  • E:代表独占(Exclesive);
  • S:代表共享(Shared);
  • I:代表已失效(Invalidated)。

M 和 I 都代表 Cache 和主内存数据不一致,即“脏”数据。E 和 S 都是一致的,但他们有区别:

  • E 代表仅当前 CPU 的 Cache 里加载了这块数据,则可以自由写入;
  • S 代表有其他 CPU 也把同一块 Cache Block 从内存加载到其 Cache中,这时候写入就需要向所有 CPU 核广播请求(RFO,Request For Ownership)。

-------------本文结束感谢您的阅读-------------

本文标题:深入浅出计算机组成原理——原理篇:存储与I/O系统(35-39)

文章作者:

原始链接:https://www.xiemingzhao.com/posts/computerOrgArc35to39.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。