# 台大 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. ?