---
# System prepended metadata

title: 第 6-1 講 Memory
tags: [計算機結構]

---

# Memory

電腦中的「記憶體」分成了很多階層：

<iframe src="https://drive.google.com/file/d/1xYmck9xYnoXQ_s4P-fDVx7NJgpbwFpAf/preview"  height="580"></iframe>


之前寫組語時接觸到的 REG 就是速度最快的記憶體，但同時他也容量最小，最昂貴。

接下來我們要接觸到的記憶體是「Cache」，REG 的下一層級，也就是 L1 L2 L3 快取，單位是「==Block==」

## Locality

記憶體能分成這麼多階層，是因為 Program 在記憶體的操作上具有「區域性」：

- Temporal Locality
    - 時間上的區域性，在一定短的時間內都是操作相同的區域，例如迴圈操作
- Spatial Locality
    - 空間上的區域性，如果操作某個區域，則鄰近區域也很有可能會一起操作，例如陣列存取

這樣的區域性，搭配較小的硬體可以有更快的速度，造就了現今的記憶體層級。

## Hit 跟 Miss

如果某個資料可以在 Cache 中找到，代表不用從 Memory 去拿，此時稱作 ==Hit==
否則就需要將資料從 Memory 搬進 Cache 裡面，此時稱作 ==Miss==。

- Hit Rate：Cache 中可以 Hit 的比率
- Miss Rate：1 - Hit Rate。
- Hit Time：Hit 所需的時間
    - 「判斷是否為 Hit」的時間，加上「讀取 Cache」的時間，不過這兩個的時間都非常短
- Miss Penalty：Miss 時需要的時間
    - 「將資料從 Memory 搬到 Cache」的時間，加上「將資料送到 Processor」的時間
    - 因為需要從 Memory 搬資料，花的時間很長
    - Hit Time << Miss Penalty

# 資料的 Address

:::warning
當 CPU 需要某個 Memory 的資料時，通常會透過 MMU (**memory management unit**) 先把地址換成實體地址(Physical Address)，然後這個地址再傳給 Cache。
:::

資料的實體地址因為是 2 進制 & Byte-Address，其實有個有趣且好用的性質，假設我們有 1 個 bytes 的資料 A 在下面的地址：

$$
A=0011\ 0011\ 1010\ 0101\ 1111
$$

那假如我們對資料做 ==分區== 的動作，例如從地址 0 開始：

- 每 16 個 bytes 為一小組
- 每 256 個 bytes 為一中組
- 每 4096 個 bytes 為一大組

我們可以注意到：

- A 所在的小組是 $0011\ 0011\ 1010\ 0101$，剛好就是他的地址 mod 16 的結果
- A 所在的中組是 $0011\ 0011\ 1010$，剛好就是他的地址 mod 256 的結果
- A 所在的大組是 $0011\ 0011$，剛好就是他的地址 mod 4096 的結果

A 換個方式可以說他在 $0011\ 0011$ 大組的 $1010$ 中組的 $0101$ 小組，並且他在小組的編號是 $1111$。

所以對於任何其他地址，我們都可以這樣快速的找到他是哪個大中小組。
這在接下來的 Cache 結構中起到很重要的作用。

# Directed-mapped cache

這種 Cache 的結構，顧名思義就是直接用地址 Map 到 Cache 裡面。

- Cache 只有有限的大小，這裡我們假設只能放 4 個 Block 的資料
- 一個 Block 這裡定義為 16 bytes

現在我們要將 0xAFFF0800 到 0xAFFF0807 這 8 個 ==Block 資料== 放到 Cache 裡面，那要怎麼知道 Cache 中誰是誰？就是上面的提到的分組概念：

- 0xAFFF0800 是 ==Block Address==，0xAFFF0800 跟 0xAFFF0801 之間實際有 16 個 bytes
    - 0xAFFF0801 各 16 個 bytes 的地址是 0xAFFF0801==0== ~ 0xAFFF0801==F==
- 因為 Cache 只能放 4 個 Block，所以==Block 地址 mod 4== 就是該 Block 在 Cache 中用來放置跟查找的==編號 Index==。
    - 例如 0xAFFF0801 就會放在 Cache 中==編號 1== 的位置
- 但是 0xAFFF0800 跟 0xAFFF0804 都會對應到 Cache 中的==編號 0==，如果 Cache 中有==編號 0== 的話我要怎麼知道他是誰？
    - 所以 Cache 中會把 0xAFFF0800 除以 Cache 大小(目前的情境是 4 個)的部分作為==標籤 Tag==
    - 0xAFFF080~16~0001~2~ 就是 0xAFFF0804 的 ==標籤==
    - 別忘記地址是 16 進制， 4~16~=0100~2~ 除以 4 會得到 0001~2~
    - 當我們想拿 0xAFFF0804 的資料時
        - 先檢查 Cache 中 ==編號 0== 有沒有資料
        - 有的話檢查 ==標籤== 是不是 0xAFFF0804 的標籤
- 如果我們只想要 0xAFFF0804 這個 Block 的第 3 個 byte
    - 別忘記 0xAFFF0804 是 block address
    - 所以實際上 0xAFFF08043 就是第 3 個 byte 的地址
    - 地址中 3 這個值就代表第 3 個 byte 在這個 block 的 ==offset 位移==
:::info
所以當 CPU 想要某 1 個 byte 時，會傳一個地址給 Cache，這個 Cache 根據上面的流程可以分成 ==Tag==、==Index== 跟 ==offset== 3 個部分。
這 3 個部分就是上面提到的 3 個大中小組。
:::

## 電路圖

下圖是電路結構圖，但是其實算是化簡的版本：

<iframe src="https://drive.google.com/file/d/1NaqwK4txXcMP-R978JSryf50ipHDfo3S/preview"  height="580"></iframe>

<!-- ![](https://drive.google.com/uc?id=1NaqwK4txXcMP-R978JSryf50ipHDfo3S&export=download) -->

<iframe src="https://drive.google.com/file/d/18ln6QGNq7ayajR-_igR7tsA-FylkFlQx/preview"  height="280"></iframe>

<!-- ![](https://drive.google.com/uc?id=18ln6QGNq7ayajR-_igR7tsA-FylkFlQx&export=download) -->

這就是上面提到的，Memory Address 會被拆成 3 個部分，Offset 是小組，Index 是中組，Tag 是大組。

- 地址通常是 32 bit，代表 Memory 中可以有 4G 的資料
- 對照上面的電路圖，可以看到 Tag 就會拿去跟 Cache 中的 Tag 來比較 (那個 = 電路)
- Index 就會決定要拿 Cache 中的哪一個 Block 資料，==或者說第幾個 Set(見下方)==
- Offset 決定要這個 Block 的第幾個(作為 MUX 的 flag)

我們聚焦到 Cache 的部分：

<iframe src="https://drive.google.com/file/d/1EWFUgQUlpiB6h2WKjYSCYIxnJV3QcFZF/preview"  height="380"></iframe>

<!-- ![](https://drive.google.com/uc?id=1EWFUgQUlpiB6h2WKjYSCYIxnJV3QcFZF&export=download) -->
>我 Sets 忘記加 s

這個部分就是我們的 Cache，一個 Cache 是由很多個 Sets 組成；每個 Set 裡面則有：

- Valid Bit
    - 這個是一個用來判斷的 Bit，下面會介紹
- Cache Tag
    - 當一個 block 資料放進 Cache 時，要記得紀錄 Tag
- Cache Data
    - 就是一個 Block 裡面的資料，上圖中一個 Block 為 16 Bytes
    - 當我們講 1 KB Direct Mapped Cache，**這個 1 KB 是指 Cache Data 的大小**
    - 所以上圖就是 256x16=4096=4 KB 的 Direct Mapped Cache

:::warning
所以總結來說，對於一個 $2^n$ bytes direct-mapped cache，並且 Block size 是 $2^m$ Bytes：

- Bytes Offset 需要 $m$ 個 bit
- Cache Index 需要 $n-m$ 個 bit
    - 因為 Sets 數量為 $2^n÷2^m=2^{n-m}$ 個
    - 所以需要 $n-m$ 個 bit 來定位 set
- Cache Tag 需要 $32-(n-m)-m=32-n$ 個 bit
    - 換個角度說，這 $n$ 個 bit 可以決定一個 byte 在哪裡
    - $n-m$ 決定第幾 row，$m$ 決定第幾 col
:::

:::info
有些題目會特別問 Cache Size，此時極有可能是在問 valid + tag + data 的部分，而不是只有 data 的部分。
:::

## 手算

- Cache 有 64 個 block，每個 block 16 個 Bytes
- 某個資料在 memory 中的地址為 1200~10~：

$$
\displaylines{
\left \lfloor \frac{1200}{16} \right \rfloor = 75\\
75\ \text{mod}\ 64=11 
}
$$

可知他在第 11 個 block；而 1200 mod 16 = 0，所以在第 0 個 offset。
但是我們也可以先這樣做：

$$
1200_{10}=4\text{B}0_{16}=0100\ 1011\ 0000_{2}
$$
最後 4 個 bit 是 offset，64 個 block 需要 6 個 bit 作為 index，那 6 個 bit 就是 001011=11。

## Block Size

如果我們有很大的 Block Size：

- 優點：
    - 我們在空間區域性上可以有很好的優勢。
- 缺點：
    - Cache 大小有限，Block Size 越大 Sets 數量會越少，也就代表越容易發生競爭 competition，反而可能夠容易發生碰撞。
    - 並且還會有著更長的 miss penalty 時間
    
miss penalty 目前有一些方法可以緩解：

- Early restart
    - 在搬運的過程，一旦發現已經搬到想要的 Bytes 了，就不繼續搬，直接結束
    - 所以有可能不會把完整的 Block 搬回來
- Requested word first 
    - 不管其他 Bytes，直接鎖定我要的 Bytes 搬進來就好
        
# Read Write Handling

接著來細講要怎麼讀寫 Cache。

## Handling Read

Read 跟上面的介紹很像，就是去 Cache 裡面翻看有沒有資料：

1. Cache index 找到正確的 entry
2. Cache tag 判斷是否為想要的資料
3. Valid bit 看是不是 Valid
4. Btyes offset 找到想要的 Bytes

Valid bit 見下方：)

## Read Miss Policy

那 4 步有一個失敗的話就要從 Memory 去搬。

**搬好後 Valid bit 要設為 1**。
>因為剛開機時 Valid bit 會被設為 0

## Handling Writes

Write，有兩種 Policy：

- ==Write through==
    - 資料除了會寫進 Cache，還會寫回 Memory
    - 資料是同步的 Consistence
- ==Write back==
    - 資料只寫進 Cache，設置「==Dirty Bit==」
    - 如果有一筆資料即將因為一個 Read 而被替換 ==Eviction==掉
        - 先檢查 ==Dirty Bit==，看他是不是某個 write back 的資料
        - 如果是的話，就要先寫回 Memory

## Write Miss Policy

有 2 個 Policy，加上因為 write 有兩種，所以實際上有 4 種情況：

- Write allocate (fetch on write)
    - 將要寫入的資料==搬到 Cache==
    - WT：將資料搬到 Cache，再寫入 Cache & Memory
    - WB：將資料搬到 Cache，一樣只寫在 Cache，設置 dirty bit
        - 由於資料會搬進 Cache，所以 write miss 是有可能導致資料寫回 memory 的
- No-write allocate (write-around)
    - 不會把要寫入的資料搬到 Cache
    - WT：因為沒有搬進 Cache，所以只會寫進 Memory
    - WB：因為沒有搬進 Cache，所以只會寫進 Memory
    - 所以 write-around 的 Cache 資料都是 read 時搬的

### 優缺點

- WT
    - 優點：資料保持一致，並且不會因為某次的 Read Miss 導致的替換，導致需要進行 Memory Write
    - 缺點：每次都需要等待寫回 Memory 的時間
- WB
    - 優點：如果對同個地址作多次寫入，就不用一直寫進 Memory，很省時間
    - 缺點：資料沒有一致性，而且每次的 Read Miss 有可能要付出更多的時間

### Write Buffer for Write Through

由於 WT 每次都要等待完整的寫回 Memory，所以聰明的人們又想出了 Buffer 的技巧。

<iframe src="https://drive.google.com/file/d/1kgWNR_0zk-gRoKsAS2cX9Qk3sEVGlLPg/preview"  height="380"></iframe>

<!-- ![](https://drive.google.com/uc?id=1kgWNR_0zk-gRoKsAS2cX9Qk3sEVGlLPg&export=download) -->


每次要寫的時候，除了一樣會寫入 Cache，原本的寫回 Memory 改成了寫入 Buffer 裡面：

- 如同其名，他就是個緩衝，寫入的速度會比 Memory 快很多
    - 他是 FIFO
    - 我猜應該是跟 Cache 同等級的
- 新增一個 Memory controller，負責將 Buffer 的內容寫回 Memory
    - 因為寫進 Buffer 很快，就不會被寫回 Memory 的時間給拖累
    
只要執行寫回指令的頻率，小於寫進 DRAM (buffer 寫進 Memory)的頻率，那麼 buffer 就起到很好的緩衝作用。
但要是反了過來， Buffer 就沒用了：)

而現在其實也有很多的 WB 也會使用 Buffer 的技術來減少 Miss Penalty。
>我猜應該是將寫回 Memory 的部分改成寫進 Buffer

# Split cache vs. Combined cache

Intel 在 Pentium 1993 開始，將 Cache 分成了 Data Cache 跟 Instruction Cache，這就是 Split cache。

- Split cache
    - 優點：higher cache bandwidth，因為可以同時讀兩種資料，bandwidth 會變成兩倍
    - 缺點：低 Hit rate，因為 Cache 總共大小不變，但是被分成了兩塊，所以 Sets 數量會變少
- Combined cache
    - 優點：Split 的相反，高 Hit rate。
    - 缺點：一樣是 Split 的相反，lower cache bandwidth

老師說現在的 CPU，大多會將：

- L1 Cache 設為 Split cache，因為要求速度，希望高的 bandwidth
- L2 或 L3 Cache 則會是 Combined cache，因為在下一層就是 Memory
    - 如果發生 Miss 就會是 Memory 層級的速度，不像 L1 發生 Miss 的話是 Cache 層級的速度
    - 因此才會採用高 Hit Rate 的 Combined cache

# Memory Design

有三種 Memory Design：

<iframe src="https://drive.google.com/file/d/1WJHS1txOcxH7rbSQtgUdmZxzcQsoSkpW/preview"  height="580"></iframe>

<!-- ![](https://drive.google.com/uc?id=1WJHS1txOcxH7rbSQtgUdmZxzcQsoSkpW&export=download) -->

- One-word-wide memory organization
    - Memory 跟 Cache 間的 Bus 寬度為 1 word (4 bytes)
    - 如果一個 address 要求讀取 1 block (16 bytes)，就要進行 4 次「Memory 讀取」
- Wide memory organization
    - Memory 跟 Cache 間的 Bus 寬度為多個 word 
    - 下面的例子中為 2 word
- Interleaved memory organization
    - Memory 跟 Cache 間的 Bus 寬度為 1 word
    - 但是資料會以 Bank 的方式儲存，每個 Bank 寬度為 1 word
    - 假設這裡總共有 4 個 Bank，因此如果 address 要求讀取 1 block，1 block 有 4 個 word，只需要進行 1 次「Memory 讀取」

前面的介紹中，一個 Memory Address 只會要求一個 Byte，那這裡怎麼就可以要求 1 block？答案是Memory Address 的結構有經過修改，我猜應該是直接不管 Btyes Offset。

那個 Bank 的結構，我推測應該就是將原本 Memory 的資料分在四個地方儲存，每個地方寬度是 1 word；這樣進行 Memory 存取時，就可以四個地方同時進行存取，也因此才會有上面的『只需要進行 1 次「Memory 讀取」』

## 模擬

下面模擬中，一次 Address 要求回傳 1 block (4 個 word)的內容，並且情境為：

- 一條 bus 寬 4 個 bytes = 1 個 address = 1 個 word
- One-word-wide memory organization：Cache 跟 Memory 之間 1 條 bus 的寬度
    - DRAM access 一次只能抓 1 個 word
- Wide memory organization：Cache 跟 Memory 之間變成 2 條 bus 的寬度
    - DRAM access 一次可以抓 2 個 word
- Interleaved memory organization：有 4 個 bank
    - 每個 bank one word 寬，1 個 bank access 一次可以抓 1 個 word
- 從 Cache 送 Address 到 Memory 要 ==1 memory bus cycle==。(簡稱 ==要求==)
- DRAM access 需要 ==15 memory bus cycle==(簡稱 ==查詢==)
- 從 Memory 傳 word 到 Cache 要 ==1 memory bus cycle== (簡稱 ==回傳==)

然後是三種情境：

- One-word-wide memory organization
    - ==要求==：1 memory bus cycle
    - ==查詢==：4 x 15 memory bus cycle
    - ==回傳==：4 x 1 memory bus cycle
    - 總共為 $1 + 4 \times 15 + 4 \times 1=65$
- Wide memory organization：
    - ==要求==：1 memory bus cycle
    - ==查詢==：2 x 15 memory bus cycle
    - ==回傳==：2 x 1 memory bus cycle
    - 總共為 $1 + 2 \times 15 + 2 \times 1=33$
- Interleaved memory organization
    - ==要求==：1 memory bus cycle
    - ==查詢==：1 x 15 memory bus cycle
        - 因為一次的 Memory Access 可以從 4 個 bank 各讀 1 word 出來
    - ==回傳==：4 x 1 memory bus cycle
    - 總共為 $1 + 1 \times 15 + 4 \times 1=20$


# Cache Performance

原本指考慮 CPU cycles，現在要多考慮 Memory 的 stall cycles：

$$
\begin{align}
&\text{CPU time} = \text{(CPU execution clock cycles + Memory stall clock cycles) × clock cycle time}\\
&\text{Memory stall clock cycles } = \text{Read-stall cycles + write-stall cycles }\\
&\text{Read-stall cycles }= \text{# of Read × Read miss rate × Read miss penalty}\\
&\text{Write-stall cycles } = \text{( # of Writes × Write miss rate × Write miss penalty) + Write buffer stalls.}
\end{align}
$$

:::warning
Memory 的 stall cycles 由 Read Write stall Cycle 所組成，但是 Hit 的話代價很小可以不管，我們只看 Miss 的 Penalty。
:::

### 如果不細分是 ready 或 write 的話

Memory stall clock cycle 則會是：

$$
\begin{align}
\text{Memory stall clock cycles } &= \text{# Memory accesses × Miss rate × Miss penalty}\\
&= \frac{\text{Instructions}}{\text{program}} × \frac{\text{Misses}}{\text{Instruction}} × \text{Miss penalty}\\
\end{align}
$$

$\text{# Memory accesses}=\frac{\text{Instructions}}{\text{program}}$ 單純是因為考量了多個 program 的可能，要把他換算成 per program 而已。

$$
\text{Average memory access time = Hit time + Miss rate × Miss penalty}
$$

# Memory Stall 的影響

如果我有個情境如下：

- $I$-Cache miss rate = 2% 
- $D$-Cache miss rate = 4%
- Base CPI 2.0
- Miss penalty = 100 cycles
- Frequency of loads and stores is 36%
- 總共有 $X$ 個指令
- Clock cycle 為 $Y$ ps

**這個情境不細分 read 跟 write，所以不會有 write buffer stall**

所以我們可以算出：

- $I$-Cache stall cycle：
    - $X\cdot 2\% \cdot 100=2X$
- $D$-Cache stall cycle：
    - $X\cdot 36\% \cdot 4\% \cdot 100=1.44X$
- CPU cycle：
    - $X\cdot 2=2X$
- CPU time with stalls：
    - $(2X+1.44X+2X)\cdot Y=5.44XY$ ps

如果是完美沒有 Miss 的 Cache CPU，則會是 $2XY$ ps

速度上相差 $\frac{5.44XY}{2XY}=2.72$ 倍。

:::warning
CPU cycle 又稱 **hit time**
:::

## DRAM 速度問題

如果此時我們將 clock rate 提升為兩倍，也就是說 Clock cycle 變為 ==$\frac{Y}{2}$== ps。
但是要注意，因為我們只提升 CPU 的 clock rate，而沒有提升 Memory cycle 的 clock rate，因此 Miss penalty 會變為 ==200 cycles==；這就是老師提到的， CPU 速度上去了，但 Memory 速度沒上去的問題。

所以我們來算一次提升 clock rate 後的時間：

- $I$-Cache stall cycle：
    - $X\cdot 2\% \cdot 200=4X$
- $D$-Cache stall cycle：
    - $X\cdot 36\% \cdot 4\% \cdot 200=2.88X$
- CPU cycle：
    - $X\cdot 2=2X$
- CPU time with stalls：
    - $(4X+2.88X+2X)\cdot \frac{Y}{2}=4.44XY$ ps

拿來跟提升前的時間來比較的話，速度上相差 $\frac{5.44XY}{4.44XY}\approx 1.23$ 倍。
理論上頻率拉高兩倍，應該可以有 2 倍的進步，但是這跟我們所期望的 2 倍相差甚遠。

---

## 回顧

- Directed Mapped Cache
- Read Write Handling
- Memory Design
- Cache Performance