contributed by < JimmyLiu0530
>
進階電腦系統理論與實作
給定每個像素 (pixel) 為 32-bit 的 RGBA 的位元圖 (bitmap),RGBA 代表紅綠藍三原色的首字母,Alpha 值則表示顏色的透明度/不透明度,其轉換為灰階影像 (gray scale) 的函式為:
人類視網膜包含三類錐形對光敏感的細胞,分別對不同波長範圍的光有反應:
上圖中的縱軸代表人眼對某對應波長的光的敏感度。其中 1 Å (埃) = 米 = [出處]
人眼吸收綠色比其他顏色敏感,也可說人眼最容易捕捉到綠色,所以當影像變成灰階時,僅僅將紅色、綠色、藍色加總取平均,不足以反映出人眼所見。常見的方法是將 ,這三個除數的總和為 256
(即 ),可使除法變簡單 (等同於右移 8 位元)。
如果我們將所有的運算都建立表格,於是
16 MB; 表格太大
如果先計算針對「乘上 0.299」一類的運算,計算後再建立表格呢?
降到 32 KB 以內; cache friendly
接著嘗試使用 NEON 指令集加速。將 RGB 三色的權重存入 r3, r4, r5 等暫存器。
vdup.8
(Vector Duplicate),分別複製到大小為 8 bit 的 NEON register d0 - d2
vld4.8
(Vector Load),載入 pixel 的資料到 4 個 8-bit 的 NEON register d4-d7,其中那個 4 為 interleave,因為我們有 ARGB,所以 gap = 4。
再來就是計算 weighted average。可用 Vector Multiply 和 Vector Multiply Accumulate 指令:
將值除以 256 就是我們要的灰階值。
vrshrn
(Vector Shift Right by immediate value)
vrshrn.u16 d4, q10, #8
最後儲存結果。
vst
(Vector Store)
vst4.8 {d4-d7}, [r3]!
效能評比如下: (對照 v0
和 v4
)
從這張表即可理解,使用 NEON 指令加速只用原本 4.6% 的時間即可完成 RGBA 轉灰階的處理。
Neon Intrinsics 允許我們避免直接撰寫組合語言,而是有如 C 函式呼叫,例如 vmull.u8
和 vmlal.u8
分別對應以下 intrinsics: (提示: 在 Neon Intrinsics 網頁搜尋 NEON intrinsics 可獲得更詳盡的描述)
考慮以下使用 float
來保存像素的程式碼:
函式 to_rgb
可將原本透過連續的 float
空間保存的資料轉為 RGB 像素,每個像素佔用 3 個位元組,即 24 位元。針對 Aarch64 (Arm 64-bit) 架構 (假設為 little-endian),下方的程式碼則可將 RGB 轉換為灰階:
請補完程式碼,使上述程式碼得以符合預期地運作。
line 4-7 初始化等等會用到的變數,像是 RGB 的權重、位移量…等。
因為本題是在 64-bit 的環境下執行,因此暫存器也是 64-bit,代表一輪可以處理 8 個像素。因此, nn
紀錄需要做的輪數,剩餘不足一輪的會在 line 33-35 單獨處理。
line 12-14 將 RGB 各顏色的權重分別載入到 64-bit 暫存器,其中載入的方法為複製 8 份 1-byte 權重(e.g. R2Y
、G2Y
、B2Y
)到暫存器中。
每輪執行以下步驟:
載入的方式是採 interleaving
,也就是說從 rgb
取到的第一個 byte 會載入到第一個暫存器,取到的第二個 byte 會載入到第二個暫存器,取到的第三個 byte 會載入到第三個暫存器,取到的第四個 byte 會載入到第一個暫存器。
vmull_u8
會將第一個 argument 與第二個 argument 的各個 byte 相乘,因此乘出來的結果就會變成 16 bits。全部的結果就會變成 16 x 8 = 128 bits。vmulalu8
就只是 vmull_u8
乘完再與另一個暫存器相加而已。
最後,將計算出來的加權值除以 256
,也就是向右位移 8
後,即為灰階值。
現在要將每個像素的 RGB 從原本 16-bit unsigned 轉成 32-bit float,因為我們原本就是使用 float
來保存資料。
轉換的方法如下:
y16
中 8 個像素的資料分成兩群各自去做轉換,因此 _ylow
、_yhigh
裏頭各自有 4 個像素的資料。_ylow
、_yhigh
存回 ptr
中。顯然,_ylow
要存回的初始位置就是 ptr
,所以OFFSET1 = 0
。因為 ylow
佔 32*4 bits,也就是 4 個 float,所以 _yhigh
要從 ptr
+ 4 開始存,因此 OFFSET1 = 4
。
- 可能會用到 ImageMagick 事先處理輸入檔案
- 參照 Bomb Lab 實作紀錄 得知如何安裝 GNU Toolchain 及 QEMU
給定 個城市,編號從 到 ,再給予一個大小為 的陣列 edges
,其中 表示城市 和 之間存在雙向邊。題目保證任兩個城市間有唯一的路徑,也就是說所有城市構成一棵樹 (tree)。
上圖的聯通關係為:
- 1, 4
- 1, 3
- 3, 2
- 1, 3, 2
- 4, 1, 3
- 4, 1, 3, 2
一棵子樹 (subtree) 是上述所有城市的子集。在子集中,任意城市間可透過子集的其他城市和邊到達。兩個子樹被認為不一樣的條件是,至少有個城市在其中一棵子樹中存在,卻又不存在於另一棵子樹中。
給定 範圍介於 到 ,請找出城市間最大距離恰好為 的所有子樹數目。請你回傳一個大小為 的陣列,其中第 個元素(下標從 1 開始)是城市間最大距離恰好等於 子樹數目。
兩個城市間距離定義是,它們之間需要經過的邊的數目。
第一組輸入:
預期輸出:
既然 ,於是輸出的陣列會有 3 個元素
- 子樹
{1,2}
,{2,3}
和{2,4}
最大距離都為1
- 子樹
{1,2,3}
,{1,2,4}
,{2,3,4}
和{1,2,3,4}
最大距離都為2
- 不存在城市間最大距離為
3
的子樹。
第二組輸入:
預期輸出:
既然 ,於是輸出的陣列只會有 1 個元素,且題目已保證任意城市之間只有唯一的路徑
第三組輸入:
預期輸出:
既然 ,於是輸出的陣列會有 2 個元素
- 子樹
{1,2}
和{2,3}
最大距離都為1
- 子樹
{1,2,3}
最大距離為2
限制:
思路:
在圖論中,環是一條只有第一個和最後一個頂點重複的非空路徑
- 不具備環的圖被稱作無環圖
- 不具備有向環的有向圖被稱做有向無環圖 (DAG,directed acyclic graph)
- 無環的連通圖被稱作樹
窮舉狀態,首先要判斷是否無效
判斷該狀態是有效後,如何求最大距離?所謂的最大距離是無向無環連通圖的最大直徑
以下程式碼是可能的解法:
參考資訊:
首先,介紹一些變數的意義:
d[i][j]
:節點 i
與節點 j
之間的最短距離conn[i]
: 與節點 i
相鄰的點,用二進位表示根據 input,將 conn
、d
做初始化,然後計算兩點間的最短距離。
上面提到,我們採用窮舉所有子樹來求解,不過我們要先去檢查該子樹是否為連通? 作法說明如下:
S
代表一顆子樹,從這子樹中 index 最小的點開始,不斷新增與此點相鄰的點進 conn_nxt
,因為我們只在乎這顆子樹的點而已,所以其他不在這子樹的點我們都忽略,對應到程式碼
透過不斷的從現有的點中往外擴展,直到收斂,因此,COND1 為 conn_nxt == ctmp
。
此時,若該子樹為連通,則 ctmp
一定包含該子樹所有的點。反之,如果
ctmp
沒有包含該子樹所有的點,也就是說 ~ctmp & S != 0
,則該子樹不連通,因此 COND2 == ~ctmp & S
。
接著確認是連通圖後,針對該子樹去找出這棵樹的 diameter,即 ,其中 為該子樹的點集合。找法說明如下:
看過所有該子圖中的點與子圖中其他點的距離,就可以找出最大的距離,即 diameter。
最後,根據該子樹的 diameter 去遞增相對應 rv[]
的位置,因此,ITER == --ret
。