# 2019q1 Homework3 (review) contributed by < `bauuuu1021` > * [作業要求](https://hackmd.io/s/S1-wcjavN) ## 第 1 周第 1 題 ### 題目 西洋棋中的皇后可以直線前進,吃掉遇到的所有棋子,如果棋盤上有 8 個皇后,則這 8 個皇后如何相安無事地放置在棋盤上,1970 年與 1971 年, E.W.Dijkstra 與 N.Wirth 曾用這個問題來講解程式設計之技巧。 關於棋盤的問題,都可以用遞迴求解,然而如何減少遞迴的次數?在八個皇后的問題中,不必要所有的格子都檢查過,例如若某列檢查過,該該列的其它格子就不用再檢查了,這個方法稱為分支修剪。 ![](https://i.imgur.com/Ntc7TDQ.png) 所以檢查時,先判斷是否在已放置皇后的可行進方向上,如果沒有再行放置下一個皇后,如此就可大大減少遞迴的次數,例如以下為修剪過後的遞迴檢 查行進路徑: ![](https://i.imgur.com/0aUWpaI.png) 8 個皇后的話,會有 92 個解答,如果考慮棋盤的旋轉,則旋轉後扣去對稱的,會有 12 組基本解。 [ [source](https://openhome.cc/Gossip/AlgorithmGossip/EightQueen.htm) ] 透過 [bitwise 演算法](http://jgpettibone.github.io/bitwise-n-queens/) 改寫為以下 C 程式: ```cpp= #include <stdio.h> #include <stdlib.h> static int sol_count = 0; void recursive_solver(int row, int maj_con, int min_con, int col_con, int n) { int queen_position; int conflicts = min_con | maj_con | col_con; int i = 0; min_con &= 65535; while (i < n) { queen_position = 1 << (A1 - i); if (!(queen_position & conflicts)) { if (row == n - 1) sol_count++; else recursive_solver(row + A2, (maj_con | queen_position) >> A3, (min_con | queen_position) << A4, col_con | queen_position, n); } i++; } } void solve_queens(int n) { int minor_dconflict = 0, major_dconflict = 0, column_conflict = 0; recursive_solver(0, column_conflict, major_dconflict, minor_dconflict, n); printf("solutions = %d\n", sol_count); } ``` 其中 `solve_queens` 的參數 `n`,數值介於 `4` 到 `16`。 請補完程式碼。 ### 解題與想法 * N-Queen problem 指的是在 n*n 大小的棋盤上放置 n 個皇后,使沒有任二皇后在同一個直行或橫列或斜線上 * 首先閱讀完 [bitwise 演算法](http://jgpettibone.github.io/bitwise-n-queens/),可對這個演算法有基本的認知 * conflicts ```Cpp=8 int conflicts = min_con | maj_con | col_con; ``` coflicts 紀錄了在哪些位子放皇后是會產生衝突的,在稍後的 ```Cpp=14 if (!(queen_position & conflicts)) { ``` 被用來檢查,若 `queen_position & conflicts` 為 0,`queen_position` 現在表示的位子可以放置皇后 * A1 ```=11 min_con &= 65535; ``` * 因為 min_con 會隨遞迴不斷變大,可能超出預期範圍 (亦即棋盤大小) 而造成錯誤,故需要在開始計算前使用 mask 使之回到正確範圍 * 根據題幹 ==其中 `solve_queens` 的參數 `n`,數值介於 `4` 到 `16`== ,可知棋盤最大應為 16\*16 ,故 mask 為 65535 (0xFFFF) ```C=12 while (i < n) { queen_position = 1 << (A1 - i); ... ``` * 每一列皇后都可能被放在 column index 15~0 這 16 個位子,對應到 1000 0000 0000 0000 ~ <br>0000 0000 0000 0001 * 因為 i 初始為 0,且 i 在 while loop 中不斷加大 * 故 queen_position 應在第一個 while iteration 最大(1000 0000 0000 0000),再隨著 iteration 次數增多而減少(至 0000 0000 0000 0001)。 * 最大應在棋盤的左上角,即 $1000 0000 0000 0000_{(2)} = 1<<15$ * ==A1 = 15== * A2 ```Cpp=18 recursive_solver(row + A2, (maj_con | queen_position) >> A3, (min_con | queen_position) << A4, col_con | queen_position, n); ``` * 根據程式初始呼叫 ```C=28 recursive_solver(0, column_conflict, major_dconflict, minor_dconflict, n); ``` 本小題討論的參數(第一個參數)為 0,表示是從第一個 row 往下逐行遞迴呼叫。 * ==A2 = 1== * A3 & A4 * major conflict (maj_con) 表示右斜對角線,minor conflict (min_con) 表示左斜對角線 * 舉例說明:假設棋盤大小為 4\*4 ,row 0 在左上角 (0,0) 放置皇后,則 `maj_con | queen_position` 值為 1000,圖示如下 | 1 | 0 | 0 | 0 | | -------- | -------- | -------- | --- | | 0 | 0 | 0 | 0 | | 0 | 0 | 0 | 0 | | 0 | 0 | 0 | 0 | * 遞迴呼叫下個 row 時 ```Cpp=18 recursive_solver(row + 1, (maj_con | queen_position) >> A3, (min_con | queen_position) << A4, col_con | queen_position, n); ``` 將 `(maj_con | queen_position) >> A3)` 設為 0100,即可避免 row 1 在 (1,1) 放置皇后 * 要達成這樣的目標可以透過將 `(maj_con | queen_position)` 執行 `>>1` * `(min_con | queen_position)` 則是執行 `<<1` * ==A3 = 1== * ==A4 = 1== ### 延伸 :::success 延伸題目: 1. 讓參數 `n` 的有效範圍比 `[4, 16]` 更大,但仍透過 bitwise 操作,並改寫為 iteration 程式; 2. 考慮到 tail-call optimization (TCO),改進上述遞迴程式碼並分析效能表現; 3. 考慮旋轉後扣去對稱的解法,求出基本解法的總量; ::: * [tail-call optimization](https://stackoverflow.com/questions/310974/what-is-tail-call-optimization) ## 第 2 周第 2 題 ### 題目 [population count](https://en.wikichip.org/wiki/population_count) 簡稱 popcount 或叫 sideways sum,是計算數值的二進位表示中,有多少位元是 `1`,在一些場合下很有用,例如計算 0-1 稀疏矩陣 (sparse matrix)或 bit array 中非 `0` 元素個數、計算兩個字串的 [Hamming distance](https://en.wikipedia.org/wiki/Hamming_weight)。Intel 在 2008 年 11 月 Nehalem 架構的處理器 Core i7 引入 SSE4.2 指令集,其中就有 `CRC32` 和 `POPCNT` 指令,`POPCNT` 可處理 16-bit, 32-bit, 64-bit 整數。 對應到 C 程式的實作: ```clike unsigned popcnt_naive(unsigned v) { unsigned n = 0; while (v) v &= (v - 1), n = -(~n); return n; } ``` 可改寫為以下常數時間的實作: ```clike unsigned popcnt(unsigned v) { unsigned n; n = (v >> 1) & 0x77777777; v -= n; n = (n >> 1) & 0x77777777; v -= n; n = (n >> 1) & 0x77777777; v -= n; v = (v + (v >> X)) & Y; v *= 0x01010101; return v >> 24; } ``` ### 解題與想法 * 首先我注意到的是 `popcnt_naive` 這個函式 ```clike= unsigned popcnt_naive(unsigned v) { unsigned n = 0; while (v) v &= (v - 1), n = -(~n); return n; } ``` * 第 4 行的 `n = -(~n)` 讓人感到困惑,但實際帶入數字: 1. n = 0 2. n = -(~n),得 n = 1 3. n = -(~n),得 n = 2 4. n = -(~n),得 n = 3 5. ... * 可發現 while 每執行一次就會使 n = n+1,故 `n = -(~n)` 作用等同於 n++ >後來發現其實有**科學**一點的想法: >* 因為 n 是無號數 >* -n = ~n + 1 >* -n - 1 = ~n >* -(~n) = n + 1 > >[name=bauuuu1021] [color=#0ABAB5] * 而 `v &= (v - 1)` 帶入數字可發現可以逐步消去最低位的 1,例如: 1. v = $1101_{(2)}$ 2. v &= (v - 1),得 $1100_{(2)}$ 3. v &= (v - 1),得 $1000_{(2)}$ 4. v &= (v - 1),得 $0000_{(2)}$,結束迴圈 5. 共執行 3 次 while loop iteration,n = 3,表 $1101_{(2)}$ 中有 3 個 1 * 接下來探討常數時間可完成操作的 `popcnt` 函式,可以用一個簡單的舉例來說明這題的核心操作:<br>假設存在一種 data type `p2_t`,可以儲存 4-bit 無號數 ```C p2_t v; // 4 bits ``` `v - v>>1` 的結果為 * $\frac{1}{2}v+1$,若 v 尾數為 1 * $\frac{1}{2}v$,若 v 尾數為 0 則以下程式碼 ```C= p2_t n; n = (v >> 1); v -= n; n = (n >> 1); v -= n; n = (n >> 1); v -= n; ``` 第 2, 4, 6 行的 n 分別表示 $\frac{1}{2}$v, $\frac{1}{4}$v, $\frac{1}{8}$v,故第 7 行執行後 v = v-(v >> 1)-(v >> 2)-(v >> 3) = $\frac{1}{8}$v + (最低 3-bit 1 數),故 v 存的為 v 之二進位表示式中 1 的數量 * 了解以上舉例後再來看 `popcnt` 函式就變得簡單許多 ```clike= unsigned popcnt(unsigned v) { unsigned n; n = (v >> 1) & 0x77777777; v -= n; n = (n >> 1) & 0x77777777; v -= n; n = (n >> 1) & 0x77777777; v -= n; v = (v + (v >> X)) & Y; v *= 0x01010101; return v >> 24; } ``` * 4~9 行只是改成一次執行 8 組 4-bit 數組 * 加入 mask `0x77777777` 只是為了避免執行 `>>1` 後數組中最低位的 1 變成右邊數組的最高位 * 第 11 行是為了將這 8 組 (index 7~0) 數組兩兩相加成為 4 個新數組,並利用 mask 除去不必要的部份 * 原: | 7 |6 | 5 | 4 | 3 | 2 | 1 | 0 | |---|---|---|---|---|---|---|---| * 相加後: | 7 | 7+6 | 5 | 5+4 | 3 | 3+2 | 1 | 1+0 | |---|---|---|---|---|---|---|---| * 加入 mask: | (all 0)| 7+6 | (all 0) | 5+4 |(all 0) | 3+2 | (all 0) | 1+0 | |---|---|---|---|---|---|---|---| * ==X=4==,==Y=0x0F0F0F0F== * 第 12 及 14 行將這 4 個新數組相加,並調整回正確數值 * v *= 0x01010101 <br>(unsingned 只能存 32-bit,故不討論 overflow 部份) | (all 0)| 7+6 | (all 0) | 5+4 |(all 0) | 3+2 | (all 0) | 1+0 | |---|---|---|---|---|---|---|---| | (all 0)| 5+4 | (all 0) | 3+2 |(all 0) |1+0 | (all 0) | (all 0) | | (all 0)| 3+2 | (all 0) | 1+0 |(all 0) | (all 0) | (all 0) | (all 0) | | (all 0)| 1+0 | (all 0) | (all 0) |(all 0) | (all 0) | (all 0) | (all 0) | 相加得 | (all 0)| 7+6+5+4<br>+3+2+1+0 | (all 0) | 5+4+3<br>+2+1+0 |(all 0) | 3+2<br>+1+0 | (all 0) | 1+0 | |---|---|---|---|---|---|---|---| * return v >> 24 | (all0)| (all 0) | (all 0) |(all 0) |(all 0) | (all 0) | (all 0) | 7+6+5+4+<br>3+2+1+0 | |---|---|---|---|---|---|---|---| ### 延伸 :::success 延伸題目: 解釋原理並找出可抵抗 [timing attack](https://en.wikipedia.org/wiki/Timing_attack) 的相關程式和場景 ::: * 原理 * Timing attack 是一種透過分析不同 input 執行所需時間來破解加密的 [side channel attack](https://en.wikipedia.org/wiki/Side-channel_attack) * Timing attack 可藉由實作常數時間 function 或嚴格的 final test 來避免 * 舉例 * 共享 memory 之 processes 間可能存在 timing attack 疑慮 >Two otherwise securely isolated processes running on a single system with either cache memory or virtual memory can communicate by **deliberately causing page faults and/or cache misses** in one process, then **monitoring the resulting changes in access times** from the other. Likewise, if an application is trusted, but its paging/caching is affected by branching logic, it may be possible for a second application to determine the values of the data compared to the branch condition by monitoring access time changes; in extreme examples, this can allow recovery of cryptographic key bits. 若一個程式 A 的 paging/caching 與 branch 執行與否有關,另一個共享 paging/caching device 的程式 B 可能藉由分析程式 A 的 access time,來得知程式 A 進行 branch condition 比較時所使用的 data value,在極端狀況下甚至可能還原加密密鑰 * Secure and Insecure string comparison * insecure ```Basic= Function InsecureCompare(StrA As String, StrB As String, length As Integer) As Boolean Dim result As Boolean For i = 1 To length If Mid(StrA, i, 1) <> Mid(StrB, i, 1) Then Exit For Next result = (i > length) InsecureCompare = result End Function ``` * secure ```Basic= Function SecureCompare(StrA As String, StrB As String, length As Integer) As Boolean Dim result As Boolean For i = 1 To length result = result Or (Asc(Mid(StrA, i, 1)) Xor Asc(Mid(StrB, i, 1))) Next SecureCompare = Not result End Function ``` 兩者不同的點在於前者只要找到相異處就會跳出迴圈並 return 結果,因此兩字串第一個 bit 就相異或最後一個 bit 才相異會有不同的執行時間;而後者則是不管相異的是哪個 bit 執行時間都相同。故後者可以避免 timing attack。 * 延伸問題的延伸問題 : `strcmp` 屬於 insecure or secure string comparison? * 以下是一個使用舉例,可以發現在完全相符、第一個不相符處前者 ascii 值較大、第一個不相符處前者 ascii 值較小會分別輸出 0、1、-1 ```C= #include <stdio.h> #include <string.h> int main() { printf("%d\n", strcmp("bauuuu", "bauuuu")); // output 0 printf("%d\n", strcmp("bauuuu", "baUUUU")); // output 1 printf("%d\n", strcmp("baUUUU", "bauuuu")); // output -1 } ``` * 從 [strcmp](https://code.woboq.org/userspace/glibc/string/strcmp.c.html) 的 glibc source code 中節錄如下 ```C= int STRCMP (const char *p1, const char *p2) { const unsigned char *s1 = (const unsigned char *) p1; const unsigned char *s2 = (const unsigned char *) p2; unsigned char c1, c2; do { c1 = (unsigned char) *s1++; c2 = (unsigned char) *s2++; if (c1 == '\0') return c1 - c2; } while (c1 == c2); return c1 - c2; } ``` 根據第 14 行可發現只要有字元不相符即跳出迴圈,故 `strcmp` 屬於 **insecure string comparison** ## 第 4 周 (下) ### 題目 考慮略為複雜的 reference counting 實作,如下: ```clike #include <limits.h> #include <stddef.h> #include <stdlib.h> /** * Destructor callback. * \param ptr Pointer to space to destroy and free */ typedef void (*rc_dtor)(void *ptr); typedef int volatile refcnt; #define REFCOUNTER_MAX (INT_MAX / 2) /* FIXME: reference counting should be robust against race conditions in * multi-threaded applications. Use C11 atomics. */ static inline refcnt refcnt_acquire(refcnt *dest) { return (*dest += 1) - 1; } static inline refcnt refcnt_release(refcnt *dest) { return (*dest -= 1) + 1; } struct refcnt_entry { refcnt n; rc_dtor dtor; void *ptr; }; /** * \brief Allocate some reference counted space * \param sz Size of space in bytes * \param dtor Destructor to use in case of free * \return the space on sucess, NULL otherwise */ void *rc_malloc(size_t sz, rc_dtor dtor) { size_t const static rc_offset = offsetof(struct refcnt_entry, ptr); size_t const static max_size = (WW) - offsetof(struct refcnt_entry, ptr); if (sz >= max_size) goto reject; void *out = malloc(sz + rc_offset); if (out) { struct refcnt_entry *const outref = (struct refcnt_entry *) out; outref->n = 1; outref->dtor = dtor; return XX; } reject: return NULL; } /** * \brief Acquire a reference to some space * \param ptr pointer, previously allocated with `rc_malloc` * \return the same pointer on sucess, NULL otherwise */ void *rc_acquire(void *ptr) { size_t const static rc_offset = offsetof(struct refcnt_entry, ptr); struct refcnt_entry *const ptrref = (struct refcnt_entry *) (void *) (((char *) ptr) - rc_offset); /* try to acquire a reference */ refcnt next = refcnt_acquire(&ptrref->n); if (YY) return ptr; /* artifically refuse the reference */ refcnt prev = refcnt_release(&ptrref->n); if (prev == 1) { if (ptrref->dtor) (*ptrref->dtor)(ptr); free(ptrref); } return NULL; } /** * \brief Release a reference to some space * \param ptr pointer, previously allocated with `rc_malloc` * \note If the reference count drops to zero, the space is freed. */ void rc_release(void *ptr) { size_t const static rc_offset = offsetof(struct refcnt_entry, ptr); if (!ptr) return; struct refcnt_entry *const ptrref = (struct refcnt_entry *) ZZ; /* release the reference */ refcnt prev = refcnt_release(&ptrref->n); if (prev == 1) { if (ptrref->dtor) (*ptrref->dtor)(ptr); free(ptrref); } } /* -- unit test -- */ #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> static void test_destructor(void *); static void *test_thread(void *); struct test_box *test_constructor(int j); struct test_box { int j; }; struct test_box *test_boxes[10] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; int test_repeat_count = 1000000; struct test_box *test_constructor(int j) { struct test_box *box = rc_malloc(sizeof(struct test_box), &test_destructor); assert(box != NULL); box->j = j; fprintf(stderr, "created item %d\n", box->j); return box; } void test_destructor(void *arg) { struct test_box *const box = (struct test_box *) arg; fprintf(stderr, "destroying item %d\n", box->j); box->j = -1; return; } void *test_thread(void *arg) { int const thread_id = *(int *) arg; int const repeat_count = test_repeat_count; int personal_refs[10]; int faults = 0; int overdrops = 0; int outstanding = 0; fprintf(stderr, "[Thread %i starting]\n", thread_id); for (int i = 0; i < 10; ++i) { personal_refs[i] = 0; } for (int i = 0; i < repeat_count; ++i) { int const step_rand = rand(); int command = step_rand % 2; int j = (step_rand / 2) % 10; if (command == 0) { /* acquire */ struct test_box *box; box = rc_acquire(test_boxes[j]); if (box == test_boxes[j]) { personal_refs[j] += 1; } else faults += 1; } else { /* release */ if (personal_refs[j] > 0) { rc_release(test_boxes[j]); personal_refs[j] -= 1; } else overdrops += 1; } } for (int i = 9; i >= 0; --i) { while (personal_refs[i] > 0) { rc_release(test_boxes[i]); outstanding += 1; personal_refs[i] -= 1; } } fprintf(stderr, "[Thread %i done: %i faults, %i overdrops, %i outstanding]\n", thread_id, faults, overdrops, outstanding); return NULL; } int main(int argc, char **argv) { int corrupt_count = 0; srand((int) (time(NULL))); /* make the boxes */ { for (int j = 0; j < 10; ++j) { test_boxes[j] = test_constructor(j); } } /* run thread start code */ { int thread_i = 0; test_thread(&thread_i); } /* inspect boxes */ { for (int j = 0; j < 10; ++j) { if (test_boxes[j]->j != j) { fprintf(stderr, "Box %i was corrupted.\n", j); corrupt_count += 1; } rc_release(test_boxes[j]); test_boxes[j] = NULL; } } return corrupt_count == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } ``` 預期的輸出如下: (其中一例) ``` created item 0 created item 1 created item 2 created item 3 created item 4 created item 5 created item 6 created item 7 created item 8 created item 9 [Thread 0 starting] [Thread 0 done: 0 faults, 2575 overdrops, 2721 outstanding] destroying item 0 destroying item 1 destroying item 2 destroying item 3 destroying item 4 destroying item 5 destroying item 6 destroying item 7 destroying item 8 destroying item 9 ``` ## 參考資料 * [2019q1 Homework3 (作業區)](https://hackmd.io/s/BJgx6jav4) * [Secure and Insecure string comparison](https://security.stackexchange.com/questions/83660/simple-string-comparisons-not-secure-against-timing-attacks) ###### tags: `bauuuu1021`, `sysprog`,`2019spring`