---
# System prepended metadata

title: 台大 109 硬體
tags: [NTU, 硬體, '109']

---

# 台大 109 硬體
###### tags: `NTU` `109` `硬體`

1.
(a)
```c=
float conv(float *f, float *g, int n, int M)
{
    float res = 0.0;

    for (int m = -M; m <= M; m++) {
        res += f[n-m] * g[m];
    }
    return res;
}
```

(b)
```c=
    // f is in x0
    // g is in x1
    // n is in x2
    // M is in x3
.L2
    mov x5, #0              // 1. float res = 0.0
    sub x4, xzr, x3         // 2. int m = -M
.L0
    bgt x4, x3, .L1         // 3. compare m with M
    sub x8, x2, x4          // 4. n-m 
    ldr x6, [x0, x8, lsl 2] // 5. load f[n-m]
    ldr x7, [x1, x4, lsl 2] // 6. load g[m]
    fmul x7, x7, x6         // 7.
    fadd x5, x5, x7         // 8. res += f[n-m]*g[m]
    addi, x4, x4, #1        // 9. m++
    b .L0                   // 10.
.L1
    mov x0, x5              // 11.
    ret                     // 12.

```
\(c\)
- 2, 3 有 RAW
- 3 有 Control hazard
- 4, 5 有 RAW
- 5,6 和 7 有 RAW
- 7 和 8 有 RAW
- 10 有 Control hazard

(d)
可用一個暫存器，紀錄 branch 的歷史。比方說可用 1-bit 或是 2-bit 的 branch preditor。搭配 speculative execution，如果預測夠準，Control hazard 的負面效應會下降很多。

(e)
類似這樣
```c=
    // f is in x0
    // g is in x1
    // n is in x2
    // M is in x3
.L0
    bgt x4, x3, .L1           // compare m with M
    sub x8, x2, x4            // n-m 
    subi x9, x8, #1           // n-(m+1)
    addi x12, x4, #1          // m+1
    ldr x6, [x0, x8, lsl 2]   // load f[n-m]
    ldr x10, [x0, x9, lsl 2]  // load f[n-(m+1)]
    ldr x7, [x1, x4, lsl 2]   // load g[m]
    ldr x11, [x1, x12, lsl 2] // load g[m+1]
    fmul x7, x7, x6           
    fmul x11, x11, x10
    fadd x5, x5, x7           // res += f[n-m]*g[m]
    fadd x5, x5, x11          // res += f[n-(m+1)]*g[m+1]
    addi, x4, x4, #2          // m += 2
    b .L0

```
只要搭配適當的指令排序和暫存器分配，就可以減少相依指令的 stall。

(f)

先來看 M = 1 的情況
第一次存取 f 的時候會 miss，再來存取 g 的時候也會 miss，然後把 f 給踢出去，所以 f 下次要存取時還是會 miss。因此 M=1 時，miss rate 是 100%

再來看 M = 4 的情況
假設 CPU 抓取資料時，是抓取 16 bytes 對齊的 data block。
再來假設 g 是從第 0 個 block 開始放，f 是從第 3 個 block 開始放。
- 第  1 次存取 f 時會 miss，然後放在第 3 個 block。
- 第  1 次存取 g 時會 miss，然後於在第 0 個 block。
- 第  5 次存取 f 時會 miss，然後放在第 2 個 block。
- 第  5 次存取 g 時會 miss，然後於在第 1 個 block。
- 第  9 次存取 f 時會 miss，然後放在第 1 個 block。
- 第  9 次存取 g 時會 miss，然後於在第 2 個 block。
- 第 13 次存取 f 時會 miss，然後放在第 0 個 block。
- 第 13 次存取 g 時會 miss，然後於在第 3 個 block。
miss rate 為 $\frac{1}{4}$

M = 8 的情況也一樣，$\frac{1}{4}$

(g) 呃...blocking (?口?)
(h) 可以。利用 pthread 等 multithreading library 即可。
(i) 也可以。做法同上
:::info
它到底想考什麼...？
只是做 convolution 的話，根本不需要寫入記憶體，不會有 cache coherence 的問題。根據這前提，i 跟 h 基本上是一樣的問題吧
:::

(j)
- 總共有 8 個指令，共 32 bytes。當中有 2 個 FLOP (fmul, fadd)，所以 arithmetic intensity 為 $\frac{1}{16}$
- attainable performance 為 1.0 GFLOPs/sec
- 加入快取記憶體後，記憶體頻寬將不會是限制，「理論上」可達到最高的性能，也就是 16 GFLOPs/sec。
- 加入 vector unit 後，性能提升了 4 倍，所以 roofline 的水平線會往上走到 64GFLOPs/sec。但這對於我們的 kernel 沒什麼影響，因為我們的 intensity 才 $\frac{1}{16}$，效能瓶頸是在記憶體。要能充分利用 vector unit，必須改寫程式碼。

2.
(a) NO，不是所有程式碼都適合平行化。如果很多個 thread 都會同時存取同一個全域變數，那平行化並不會帶來多大的效益

(b) YES，記憶體越多，理論上 page fault rate 就會越低

\(c\) YES，如果每個 process 所需的執行時間都一樣長的話

(d) YES，如果記憶體夠大，不會發生 thrashing 的話

(e) YES

(f) ?

(g) YES

(h) NO，現在的 C library 都會做 buffering，看似寫入，但其實只是先暫存在程序自己的定址空間，其它程序並不會看到。

(i) YES

(j) ?

3. ?
4. ?
5. ?