---
tags: 進階電腦系統理論與實作, NCKU Linux Kernel Internals, 作業系統
---
# 2020q3 Homework10 (quiz10)
contributed by < `RusselCK` >
###### tags: `RusselCK`
> [ Arm 處理器 & dynamic programming 練習題](https://hackmd.io/@sysprog/2020-quiz10)
## Q1. 彩色圖片轉灰階
- [ ] [ARM NEON 範例](https://hackmd.io/FnDnLZcDQJOMqTHaPlsfVw?both#ARM-NEON-%E6%A1%88%E4%BE%8B%E5%88%86%E6%9E%90)
- [ ] [Neon Intrinsics](https://developer.arm.com/architectures/instruction-sets/simd-isas/neon/intrinsics)
### 使用 float 來保存像素
```c=
#include <stdint.h>
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
uint8_t *to_rgb(const float *m, int w, int h, uint8_t *rgb)
{
int size = w * h;
const float *ptr0 = m + (0 * size);
const float *ptr1 = m + (1 * size);
const float *ptr2 = m + (2 * size);
uint8_t *ret = rgb;
#define SATURATE_CAST_UCHAR(X) (uint8_t) MIN(MAX((int) (X), 0), 255);
for (int remain = size; remain > 0; remain--) {
rgb[0] = SATURATE_CAST_UCHAR(*ptr0);
rgb[1] = SATURATE_CAST_UCHAR(*ptr1);
rgb[2] = SATURATE_CAST_UCHAR(*ptr2);
rgb += 3;
ptr0++, ptr1++, ptr2++;
}
#undef SATURATE_CAST_UCHAR
return ret;
}
```
### 針對 Aarch64 (Arm 64-bit) 架構 (假設為 little-endian) 的程式碼
```c=
#include <arm_neon.h>
float *rgb_to_gray(const uint8_t *rgb, int w, int h, float *m)
{
const uint8_t Y_shift = 8;
const uint8_t R2Y = 77, G2Y = 151, B2Y = 28;
float *ptr = m;
int size = w * h;
int nn = size >> 3;
int remain = size - (nn << 3);
uint8x8_t _R2Y = vdup_n_u8(R2Y);
uint8x8_t _G2Y = vdup_n_u8(G2Y);
uint8x8_t _B2Y = vdup_n_u8(B2Y);
for (; nn > 0; nn--) {
uint8x8x3_t _rgb = vld3_u8(rgb);
uint16x8_t y16 = vmull_u8(_rgb.val[0], _R2Y);
y16 = vmlal_u8(y16, _rgb.val[1], _G2Y);
y16 = vmlal_u8(y16, _rgb.val[2], _B2Y);
y16 = vshrq_n_u16(y16, Y_shift);
float32x4_t _ylow = vcvtq_f32_u32(vmovl_u16(vget_low_u16(y16)));
float32x4_t _yhigh = vcvtq_f32_u32(vmovl_u16(vget_high_u16(y16)));
vst1q_f32(ptr + OFFSET1, _ylow); // OFFSET1 = 0
vst1q_f32(ptr + OFFSET2, _yhigh); // OFFSET2 = 4
rgb += 3 * 8, ptr += 8;
}
for (; remain > 0; remain--) {
*ptr = (rgb[0] * R2Y + rgb[1] * G2Y + rgb[2] * B2Y) >> Y_shift;
rgb += 3, ptr++;
}
return m;
}
```
## Q1. 整合 [libattopng](https://github.com/misc0110/libattopng),輸出 RGBA 和灰階的 PNG 圖片
### Set up ARM playground
> - [ ] [How to set up an ARM64 playground on Ubuntu 18.04](https://dev.to/offlinemark/how-to-set-up-an-arm64-playground-on-ubuntu-18-04-27i6)
- [x] [How to launch ARM aarch64 VM with QEMU from scratch.](https://futurewei-cloud.github.io/ARM-Datacenter/qemu/how-to-launch-aarch64-vm/)
* 確認是否準備成功
```shell
$ uname -m
aarch64
```
- [x] [lab0 - 開發環境設定](https://hackmd.io/@sysprog/2020-lab0#-%E9%96%8B%E7%99%BC%E7%92%B0%E5%A2%83%E8%A8%AD%E5%AE%9A)
> - [ ] [Linux Command 命令列指令與基本操作入門教學](https://blog.techbridge.cc/2017/12/23/linux-commnd-line-tutorial/)
> - [ ] [GNU Debugger Tutorial](https://www.tutorialspoint.com/gnu_debugger/index.htm)
* git 上傳
- [ ] [Git 使用教學](https://zlargon.gitbooks.io/git-tutorial/content/?q=)
- [ ] [GitHub 設定指引](http://wiki.csie.ncku.edu.tw/github)
```shell
$ git add .
$ git status
$ git commit -m "..."
$ git push
```
### ImageMagick
- [ ] [Install ImageMagick from Source](https://imagemagick.org/script/install-source.php)
```shell
$ sudo apt-get install imagemagick
```
- [ ] [Getting started with ImageMagick](https://opensource.com/article/17/8/imagemagick)
### [libattopng](https://github.com/misc0110/libattopng)
**Usage**
Using libattopng is as simple as adding both `libattopng.h` and `libattopng.c` to your project.
### 開始作業
* [/quiz10/rgb_to_gray.c](https://github.com/RusselCK/sysprog2020/blob/master/quiz10/rgb_to_gray.c)
編譯
```shell
$ gcc -o rgb_to_gray rgb_to_gray.c -mfpu=neon
```
出現錯誤
```
In file included from rgb_to_gray.c:3:
In function ‘vshrq_n_u16’,
inlined from ‘rgb_to_gray’ at rgb_to_gray.c:30:9:
/usr/lib/gcc/arm-linux-gnueabihf/8/include/arm_neon.h:4394:22: error: argument 2 must be a constant immediate
return (uint16x8_t)__builtin_neon_vshru_nv8hi ((int16x8_t) __a, __b);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```
## Q1. 引入 loop unrolling 和 prefetch 指令,嘗試改進 NEON 程式碼效率
### [Raspberry Pi 4 B](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/)
- [x] [Installing operating system images](https://www.raspberrypi.org/documentation/installation/installing-images/?fbclid=IwAR26V11lRLR4Tq1PjdqAQoZhWmVPjua0weA_eHl74FI-BwR22slIezS8beg)
- [ ] [[基礎] 從序列埠登入到 Raspberry Pi](https://www.raspberrypi.com.tw/1999/connect-to-raspberry-pi-via-serial/)
> 透過 USB 轉 TTL 序列傳輸線,就可以在不需要螢幕和鍵盤滑鼠的情況下登入 Raspberry Pi
> * [PL2303 Windows Driver Download](http://www.prolific.com.tw/US/ShowProduct.aspx?p_id=225&pcid=41)
- [x] [如何在沒有螢幕或鍵盤的情況下設置Raspberry Pi](https://hackmd.io/@ShenTengTu/r1baxV4pQ)
- [ ] [[基礎] 以 VNC 和 Raspberry Pi 連線](https://www.raspberrypi.com.tw/586/setting-up-vnc/)
- [ ] [如何设置树莓派 VNC 的分辨率](https://shumeipai.nxez.com/2019/07/08/set-the-resolution-of-the-raspberry-pi-vnc.html)
### Prefetch
- [ ] [数据预取 __builtin_prefetch()](https://www.cnblogs.com/dongzhiquan/p/3694858.html)
- [ ] [How to use pld instruction in ARM](https://stackoverflow.com/questions/16032202/how-to-use-pld-instruction-in-arm)
## Q2. LeetCode [1617. Count Subtrees With Max Distance Between Cities](https://leetcode.com/problems/count-subtrees-with-max-distance-between-cities/)
給定 `n` 個城市,編號為從 `1` 到 `n`,再給予一大小為 `n−1` 的陣列 `edges`,其中 **`edges[i]=[ui,vi]`** 表示城市 `ui` 和 `vi` 之間存在雙向邊。
題目保證任意城市之間有唯一的路徑,也就是說所城市構成一棵**樹 (tree)**。
一棵**子樹 (subtree)** 是上述所有城市的子集,在子集中,任意城市間可透過子集的其他城市和邊到達。
兩個子樹被認為不一樣的條件是,至少有個城市在其中一棵子樹中存在,卻又不存在於另一棵子樹中。
給定 `d` 範圍介於從 `1` 到 `n−1`,請你找出城市間 **最大距離** 恰好為 `d` 的所有子樹數目。
請你回傳一個小為 `n−1` 的陣列,其中第 d 個元素 **(下標從 1 始)** 是城市間 **最大距離** 恰好等於 `d` 的子樹數目。
> 兩個城市間 **距離** 定義是,它們之間需要經過的邊的數目。
```c=
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define ITERATE(i, begin, end) \
for (int i = (begin), i##_end_ = (end); i < i##_end_; i++)
//for (int i = (begin); i < (end); i++)
int *countSubgraphsForEachDiameter(int n,
int **edges,
int edgesSz,
int *edges0sz,
int *rsz)
{
uint8_t d[n][n]; /* distance */
memset(d, 0xFF, sizeof(d));
```
`d[n][n]` : 任兩個城市的距離
* `#18` : 距離初始為 $\infty$
> 實際上只要大於 `n-1` 即可
:::warning
* `d[i][j]` = `d[j][i]`,應該可以有更節省空間的方式
:::
```c=19
uint16_t conn[n];
ITERATE (i, 0, n)
conn[i] = 1 << i;
```
`conn[n]` : 與城市`n` 相連的 bitmask
* `#20、21` : (初始) 城市`n` 必定和自己相連
```c=22
for (int z = 0; z < edgesSz; z++) {
int *e = edges[z];
int u = e[0] - 1, v = e[1] - 1;
d[u][v] = d[v][u] = 1;
conn[u] |= 1 << v, conn[v] |= 1 << u;
}
```
從 `edges[]` 中得到相連狀況並更新 `d[][]` 和 `conn[]`
* `edges[z]` 的內容是 1-indexed,因此需要 `-1` 改成 0-indexed
```c=29
ITERATE (k, 0, n)
ITERATE (i, 0, n)
ITERATE (j, 0, n)
d[i][j] = MIN(d[i][j], d[i][k] + d[k][j]);
```
更新 `d[][]`,將 兩步 以上可達的兩個城市的距離記錄下來
* 更新後,若 `d[i][j]` 仍為 `0xFF`,代表 城市`i` 無法到達 城市`j`,反之亦然
:::warning
- [ ] 這部分還有更省時的作法嗎?
:::
```c=34
int *rv = calloc(n - 1, sizeof(int));
```
`rv` : 要回傳的陣列
> `calloc(n - 1, sizeof(int))` : 配置 `n - 1` 個 `int` 的陣列,並初始為 `0`
:::warning
- [x] `int *rv = calloc(n - 1, sizeof(int));` 可以改寫成下列形式嗎?
```c
int rv[n - 1];
memeset(rv, 0, sizeof(rv));
```
* 不行,會出現 runtime error: load of null pointer of type 'int' 的錯誤
參考資料:
* [runtime error: load of null pointer of type 'const int' 错误提示](https://blog.csdn.net/qq_34824576/article/details/86496130)
:::
### 窮舉法 + DFS/BFS
```c=35
ITERATE (S, 1, (1 << n)) {
```
窮舉所有的子樹 (bitmask 為 `S`)
```c=36
int ctmp = 1 << __builtin_ctz(S);
while (1) {
int conn_nxt = ctmp;
ITERATE (d, 0, n)
if ((ctmp >> d) & 1)
conn_nxt |= conn[d];
conn_nxt &= S;
if (COND1) // COND1 = conn_nxt == ctmp
break;
ctmp = conn_nxt;
}
if (COND2) // COND2 = ~ctmp & S
continue;
```
判斷 `S` 是否為合法的子樹 ( 即 是否為無環連通圖 )
> BFS 非遞迴版本
* `ctmp` : 目前這層 ( k 步內 ) 可以走訪到的城市
* `conn_nxt` : 下一層 ( (k + 1) 步內 ) 可以走訪到的城市
`#36` : 將 `S` 中編號最小的城市當作 root,並納入 `ctmp`
`#39 ~ 41` : 走訪目前這一層可到的城市 (`ctmp`),並將下一層可以走訪的城市納入 `conn_nxt`
`#42` : `conn_nxt &= S` : 將下一層可走訪的城市先縮在 `S` 有涵蓋的城市
`#43、44` : 若 `conn_nxt == ctmp`, 代表已經走訪完所有可走訪的城市,可以跳出 BFS
`#45` : 若否,則 `conn_nxt` 設置成下一層的 `ctmp`
:::info
`#47、48` : 若 `~ctmp & S` ,代表 `S` 並非無環連通圖,即不為合法子樹
:::
```c=49
uint8_t ret = 0;
ITERATE (i, 0, n)
if ((S >> i) & 1)
ITERATE (j, 0, i)
if (((S >> j) & 1) && (ret < d[i][j]))
ret = d[i][j];
if (!ret)
continue;
rv[ITER]++; //ITER = --ret
}
```
找出 `S` 中的最長距離 (`ret`),並更新 `rv[--ret]`
> `--ret` 用意 : 改成 0-indexed
```c=59
*rsz = n - 1;
return rv;
}
```
## Q2. 進階題 (更有效率的實作)
### Dynamic programming on tree
```c=
```