Problem
使用 OpenCL 印出裝置訊息。請參考課程講義。
Sample Input
no input
Sample Output
|
|
備註
可參考上方的題解頁面
解法
|
|
使用 OpenCL 印出裝置訊息。請參考課程講義。
no input
|
|
可參考上方的題解頁面
|
|
計算矩陣鏈乘積 $A_{r_1, c_1} B_{r_2, c_2} \cdots$ 的值。
|
|
有多組測資,每組第一行會有一個整數 $N$ 表示矩陣鏈上有 $N$ 個矩陣,第二行上會有 $N+1$ 個整數 $Z_i$,表示矩陣鏈的每一個行列大小,例如當 $N = 3$ 時,輸入 10 30 5 60
表示矩陣 $A_{10, 30} B_{30, 5} C_{5, 60}$ 相乘。第三行會有 $N$ 個整數,第 $i$ 個整數 $S_i$ 為第 $i$ 個矩陣生成種子。
對於每組測資輸出一行,將最後的矩陣結果輸出雜湊值。
|
|
|
|
輸出請用
printf("%u", answer);
,計算 Dynamic Programming 時,請使用 64-bit 型態紀錄,因為最慘情況下會超過 32-bit 所能容納的範圍。
充分地運用當初在演算法學到的,計算矩陣鍊乘積的最少乘法數,接著再針對優化後的乘法順序進行平行。平行可以從單純矩陣乘法,又或者針對可以同時進行矩陣乘法操作開始。甚至可以套用編譯器學到的最少暫存器算法,想辦法從少量的空間換取好的快取效果。
下述程式只針對矩陣乘法計算平行,而非兩個乘法同時進行,其一原因在於很難保證 load balance。
|
|
有 $N$ 個重量和價值分別為 $W_i, V_i$ 的物品,現在要從這些物品中選出總重量不超過 $M$ 的物品,求所有挑選方案中的總價值最大值為何。
輸入只有一組測資,第一行會有兩個整數 $N, M$ 分別表示物品數量和背包能容大的最大重量 $M$。接下來會有 $N$ 行,第 $i$ 行上會有兩個整數 $W_i, V_i$,分別為物品 $i$ 的重量和價值。
輸出一行整數,挑選方案中的最大價值 $B$。
|
|
|
|
遞迴公式
$$\begin{align*} f(i, j) = \max(f(i-1, j-w)+v, f(i-1, j)) \end{align*}$$最單純採用滾動數組完成,也就是所謂的 double buffer 的技巧,可以大幅度節省記憶體,但先決條件是他們的相依關係必須獨立。
在平行處理上的另一種解法,就是分成前 $N/2$ 個物品和後 $N/2$ 物品分開計算,之後再藉由 $\mathcal{O}(M)$ 合併答案,由於算法的限制,最多拆分兩組。而不像滾動數組的平行於最內層的迴圈,平行度有限,但在較少核心的處理上非常快速,其一原因是空間使用比較有效率。
|
|
|
|
但這個世界卻要求神通廣大、無所不知,那不聰明的我又該何去何從 …
給一張 $9 \times 9$ 數獨,必須滿足每行每列的 1 到 9 的數字不重複,同時在九個 $3 \times 3$ 方格內的 1 到 9 數字也不重複。請計算可填入的方法數。
測資只有一筆,共有九行,每一行上有九個整數,第 $i$ 行上的第 $j$ 個整數表示 $\text{grid}[i][j]$ 填入的數字,若 $\text{grid}[i][j] = 0$ 表示尚未填入。
對於每一組測資輸出一行一個整數,表示數獨共有幾種填法。
|
|
|
|
|
|
|
|
牽涉到 load balance 問題,大部分都要先廣度搜尋一次,把狀態展開後,再利用 dynamic scheduling 進行分配工作,效果會好上許多。為了解決狀態展開而後不浪費記憶體空間,IDA* 會是一種好的選擇。當然在 OpenMP 3.0 以上有提供 task 來幫忙做到類似的事情,但搜索展開會變成寫死,估計上會比較困難。
當然 DLX 在建表處理會變得很討厭,overhead 相較於其他的算法都高出許多,因此狀態展開不可以太多,因為他本身就搜得非常快,撰寫這一類搜索問題時,特別小心 #pragma omp private()
的使用,複製操作原則上都是根據 sizeof()
決定複製空間大小。因此若複製目標為函數參數,很容易只有複製到指標。
|
|
|
|
|
|
稀疏矩陣為大部份元素皆為零的矩陣,在科學與工程領域中求解線性模型時經常出現大型的稀疏矩陣。現在給予最常見的 Coordinate Format (簡稱 COO 格式),請問兩個矩陣相乘結果為何。
給予矩陣 $A_{n, m}$ 和 $B_{m, r}$,請計算稀疏矩陣相乘。
$$A_{4,4} = \begin{bmatrix} 0 & 0 & 0 & 0 \\ 5 & 8 & 0 & 0 \\ 0 & 0 & 3 & 0 \\ 0 & 6 & 0 & 0 \\ \end{bmatrix}, \qquad B_{4,4} = \begin{bmatrix} 0 & 0 & 1 & 3 \\ 0 & 5 & 2 & 0 \\ 3 & 5 & 0 & 0 \\ 0 & 2 & 0 & 0 \\ \end{bmatrix}$$根據 COO 格式,分別轉換矩陣 $A$ 和 $B$ 的所有非零元素,如下表所示
row_index |
col_index |
value |
---|---|---|
1 | 0 | 5 |
1 | 1 | 8 |
2 | 2 | 3 |
3 | 1 | 6 |
row_index |
col_index |
value |
---|---|---|
0 | 2 | 1 |
0 | 3 | 3 |
1 | 1 | 5 |
1 | 2 | 2 |
2 | 0 | 3 |
2 | 1 | 5 |
3 | 1 | 2 |
測資只有一組,第一行會有三個整數 $N, \; M, \; R$,分別表示矩陣 $A, \; B$ 的大小。下一行會有兩個整數 $N_A, \; N_B$,接下來會有 $N_A$ 行,每一行表示矩陣 $A$ 的 COO 格式,隨後會有 $N_B$ 行,每一行表示矩陣 $B$ 的 COO 格式。
給予 COO 格式時,先按照 row_index
由小到大,相同情況由 col_index
由小到大的方式給定,保證不會有重複,每一個元素值 $v$ 介於 $1$ 到 $2^{31}-1$ 之間。
輸出 $C_{n, r} = A_{n, m} \times B_{m, r}$ 的雜湊值。
定義 $\mathit{hash}(C_{n, r}) = \sum\nolimits_{e_{i, j} \in C \text{ and } e_{i, j} \neq 0} \mathit{encrypt}((i+1)*(j+1), e_{i, j})$,實際運作的 流程 可參考以下作法,當然你沒辦法宣告 $N \times M$ 的空間使用:
|
|
|
|
|
|
Array of Structure 簡稱 AOS,而 Structure of Array 簡稱 SOA,這兩種寫法的差異在於運用 structure 時的寫法,根據 cache line 的設計,有時候一個 struct 下會有很多變數,然而我們只需要使用部分的變數,這時候沒有使用的變數仍會跟著一起進入 cache,很容易造成 cache 效率偏低,因此回歸原始寫出 SOA 的版本。
如果是兩個稀疏方陣,用 COO 格式需要先對後面那個矩陣轉置,接著在三欄 $(i, j, v)$ 的表格下,按照字典順序排序。此時會有兩個三欄 $A(i, j, v), \; B(i, j, v)$ (別忘記 $B$ 矩陣已經轉置過),要計算 $A \times B = C$,為得到 $C_{i, j}$,$C_{i, j} = \sum A(i, k, v) B(j, k, v)$,由於已經排序好,那麼找到共同的 $k$ 不是難事,按照合併排序的手法,能在 $\mathcal{O}(N)$ 完成。
因此在最稠密的矩陣下,仍需要 $\mathcal{O}(N^3)$ 才能完成。
|
|
|
|
若採用額外空間處理,一開始就不用對 $B$ 轉置,$C_{i, j} = \sum A(i, k, v) B(k, j, v)$。每一次解決 $C$ 的第 $i$ 列上的所有元素值,此時 $A(i, k, v)$ 按照 $k$ 遞增排列,$B(k, j, v)$ 要配對 $A(i, k, v)$,會恰好掃描完整的 $B$,累加元素值會不按照順序累加到 $C_{i, j}$。
因此在最稠密的矩陣下,仍需要 $\mathcal{O}(N^3)$ 才能完成。
|
|
|
|
找出 $[ L, \; R ]$ 區間內有多少個質數。
有多組測資,每組測資一行兩個整數 $L$, $R$,表示左右區間。
對於每一組詢問輸出一行一個整數。
|
|
|
|
這一題沒有出得很好,後來想到可以作弊打表,目前仍沒有同學挑戰成功,略感傷心。
由於只要統計 $2^{31}$ 以內的質數個數,只需要把 50000 以內的所有質數找到,利用這不到 5000 的質數進行區塊篩法即可。
由於 $[1, 2^{31}]$ 也是非常龐大的區間,可以利用 bitmask 的技術,將記憶體壓縮 32 倍,但這樣子平行度還是不高,因此若詢問區間 $[l, r]$ 有多少個質數,將區間切割成數個足夠大的 chunk,這裡決定 chunk 大約在 100000 到 1000000 都有不錯的效果。
|
|
由於問題設計只針對個數,還可以偷偷本地建表完成每一個區間的值,針對非完全包含的區間單獨計算即可。打表部分可以藉由上述的程序做到。
|
|
生命遊戲中,對於任意細胞,規則如下:
每個細胞有兩種狀態-存活或死亡,每個細胞與以自身為中心的周圍八格細胞產生互動。
可以把最初的細胞結構定義為種子,當所有在種子中的細胞同時被以上規則處理後,可以得到第一代細胞圖。按規則繼續處理當前的細胞圖,可以得到下一代的細胞圖,周而復始。
輸入第一行有兩個整數 $N$, $M$,表示盤面大小為 $N \times N$,模擬週期次數 $M$。接下來會有 $N$ 行,每一行上會有 $N$ 個字符,以 0
表示 $(i, j)$ 格子上的細胞屬於死亡狀態,反之 1
為存活狀態。
對於每一組測資輸出 $N$ 行,每一行上有 $N$ 個字元表示模擬 $M$ 次的最終盤面結果。
|
|
|
|
|
|
|
|
在還沒有做任何平行前,生命遊戲的模擬非常簡單,只需要統計鄰近八格的狀態即可,然而由於是常數範圍,直接打八個加法比起迴圈去累計八格的結果快上非常多,別忽視 for loop 在做 branch 的 cycle 數量。
平行採用 big chunk 的方式,考慮到 cache miss 的關係,最好是每一個處理器都把相鄰的列放置在各自的 L1-L2-L3 cache,防止使用時不在自己的 cache,而產生嚴重的 cache miss。
|
|
|
|
相信 n-Queen 問題對每個研究 backtracking 的人來講都不陌生,這個問題是要在一個 $n \times n$ 大小的棋盤上擺n個皇后,讓她們不會互相攻擊到。為了讓這個問題更難一點,我們設計了一些障礙物在棋盤上,在這些點上不能放皇后。請留意這些障礙物並不會防止皇后被攻擊。
在傳統的 8-Queen 問題中,旋轉與鏡射被視為不同解法,因此我們有 92 種可能的方式來放置皇后。
輸入的資料最多包含 10 筆測試個案,每個測試個案的第一行會有一個整數 $n$ ($3 < n < 20$),接下來的 $n$ 行代表棋盤資料,點號 '.'
代表空的盤格,星號 '*'
代表放有障礙物的盤格。
對每筆測試個案,輸出這是第幾個 case 以及這個 case 有幾種可能的放置方式。
|
|
|
|
首先,必須先認識最快的 N 皇后問題可以利用 bitmask 的技術,把過多的 memory access 的指令捨去而加速。
在大多數的教科書上,平行只針對第一個列的位置平行,因此平行度最多 $N$,針對第一列每一個位置都分別開一個 thread 找解,這樣運算時間相當於跑最慢的那一個 thread。
|
|
為了達到每一個 thread 盡可能分到相同的工作量,避免最慘的那一條有太多的解而很慢,做好細粒度 (fine-grain) 的分配,如此一來工作量就很高的機率會相同。
因此,一開始先進行廣度搜索,把前幾列的解展開,也許會獲得 $N^x$ 種盤面,這時候平均切給 $M$ 條 thread,分配的工作量平衡上就有比較好的展現。
|
|
圖片匹配和字串匹配有一點不同,字串匹配通常要求其子字串與搜尋字串完全相符,而圖片匹配則用相似度為依據,當圖片大、複雜且具有干擾,或者需要匹配數量非常多,更先進的領域會利用特徵擷取,用機率統計的方式來篩選可能的匹配數量,篩選過後才進行圖片的細節匹配。
給予兩個圖片 $A, B$,圖片格式為灰階影像,每個像素 $\mathit{pixel}(x, y)$ 採用 8-bits 表示,範圍為 $\mathit{pixel}(x, y) \in [0, 255]$。
舉一個例子,有一個 $3 \times 3$ 的圖片 $A$ 和一個 $2 \times 2$ 的圖片 $B$,用矩陣表示如下:
$$A := \begin{bmatrix} a1 & a2 & a3 \\ a4 & a5 & a6 \\ a7 & a8 & a9 \end{bmatrix} ,\; B := \begin{bmatrix} b1 & b2\\ b3 & b4\\ \end{bmatrix}$$假設左上角座標 $(1, 1)$ 即 $a1$ 的位置、$(1, 2)$ 即 $a2$ 的位置。
比較時,整張 $B$ 都要落在 $A$ 中。現在要找到一個對齊位置 $(x, y)$,使得 $\mathit{diff}(A, B)$ 最小。
多組測資,每一組第一行有四個整數 $A_H, A_W, B_H, B_W$,分別表示圖片 $A$ 的高寬、$B$ 的高寬。
接下來會有 $A_H$ 行,每一行上會有 $A_W$ 個整數,第 $i$ 行上的第 $j$ 個整數 $x$ 表示 $A(i, j)$ 的灰階像素值。同樣地,接下來會有 $B_H$ 行,每一行上會有 $B_W$ 個整數,第 $i$ 行上的第 $j$ 個整數 $x$ 表示 $B(i, j)$ 的灰階像素值。
對於每一組測資輸出一行,兩個整數 $x, y$,滿足 $\mathit{diff}(A, B)$ 最小。若有相同情況滿足,則優先挑選 $x$ 最小,若 $x$ 仍相同,則挑選 $y$ 最小。
|
|
|
|
若圖片是 $N \times N$,樸素算法運作要 $\mathcal{O}(N^4)$,傅立葉算法要 $\mathcal{O}(N^2 \log N)$,這裡針對樸素算法平行。從最高層次的找解平行,每一個匹配位置都要耗費 $\mathcal{O}(M^2)$ 計算相似性,又由於平行限制跟核心數有關,那麼針對每一個 thread 要求找到好幾個 row 的解,分別存在各自的答案陣列區中,之後再線性跑一次取最小值,利用額外的空間儲存,避免在運算過程中遇到 critical section。
|
|
就是上列的寫法會卡在 critical section,倒不如存在另一個陣列中,效能還差距個幾百毫秒。
|
|
儘管使用快速傅立葉 (FFT) 已經達到 $\mathcal{O}(N \log N)$ 的時間複雜度的極致,只比平行樸素算法快個兩到三倍,加入平行之後,效能可以達到五到六倍加速。
FFT 用到大量的三角函數計算,一般而言若使用序列化的乘法代替三角函數計算 (疊加角度) 會比重新計算快上很多,但這樣會損失一大部分的精準度 (但速度快很多),而且會產生很多相互依賴的指令,這也導致不容易對迴圈平行。
如果需要平行,每一個三角函數可以預先存在陣列中,每一個 thread 直接存取陣列中的元素即可,但麻煩的地方在於最好使用多核處理器,然而跨越多個處理器有可能會因為快取 … 等機制,導致速度下降,因此這裡採用單一處理器六核心 (只有三個是 physical core),因此 thread 限定在五個以內。
|
|
相信不少人都已經實作所謂的矩陣乘法,計算兩個方陣大小為 $N \times N$ 的矩陣 $A, B$。為了方便起見,提供一個偽隨機數的生成,減少在輸入處理浪費的時間,同時也減少上傳測資的辛苦。
根據種子 $c = S1$ 生成矩陣 $A$,種子 $c = S2$ 生成矩陣 $B$,計算矩陣相乘 $A \times B$,為了方便起見,使用 hash 函數進行簽章,最後輸出一個值。由於會牽涉到 overflow 問題,此題作為快取實驗就不考慮這個,overflow 問題都會相同。
|
|
|
|
|
|
多組測資,每組測資一行三個整數 $N, S1, S2$。
每組測資輸出一行,一個整數 signature 的結果。
|
|
|
|
範例輸入產生 $2 \times 2$ 的矩陣。
$$A = \begin{bmatrix} 2 & 3\\ 0 & 0 \end{bmatrix} , B = \begin{bmatrix} 1 & 3\\ 3 & 0 \end{bmatrix} , A \times B = \begin{bmatrix} 11 & 6\\ 0 & 0 \end{bmatrix}$$解法跟 thread 寫法一樣,用 OpenMP 的好處在於不用處理那些 thread 的設定,用 OpenMP 提供的前處理完成執行緒的建造和移除操作。
在這裡特別提供達夫裝置 (Duff’s device) 對於迴圈展開 loop unrolling 的撰寫方式,巧妙地運用 C 語言中的 switch,相比一般寫法需要兩次迴圈處理,最後一個迴圈必須處理剩餘不整除的部分。就實作看起來,在現在版本 4.8 gcc 編譯結果下,效能沒有明顯的差別。
在高等編譯器課程中,聽說迴圈展開的數目最好不是 $2^n$,某些情況會造成 alignment 的 cache miss 的嚴重影響,實際情況還是要看怎麼運用,在這裡就不多做嘗試。
|
|
|
|
|
|