# 2017q1 Homework1 (clz) contributed by <`petermouse`> ### Reviewed by `heathcliffYang` - 用`__builtin_clz()`驗證程式的正確性是很好的想法 - 期待可以看到關於這些方法的效能分析 - 可以查詢CLZ的應用 ### Reviewed by `andycom12000` - 2^31^~2^32^-1的正確性真的有必要驗證嗎? - Commit [237f5da761f4d9c3e5a0d400781437a009dbe7d7](https://github.com/petermouse/clz-tests/commit/237f5da761f4d9c3e5a0d400781437a009dbe7d7)可以分為三個commit,不要將所有東西都混在一個commit裡面 ## 開發環境 >進度有些落後囉~ >[name=課程助教][color=red] >我會加油的QQ >[name=petermouse] * OS: Ubuntu 16.04 LTS * Memory: 8 GB * CPU: * Name: * Intel(R) Core(TM) i5-4200H CPU @ 2.80GHz * Cache: * L1d Cache: 32K * L1i Cache: 32K * L2 Cache: 256K * L3 Cache: 3072K ## 驗證程式正確性 `make PROFILE=1` 可以使程式驗證 `uint32_t` 從 1 至 2^31^-1 的正確性 不過並沒有驗證 0 與 2^31^ ~ 2^32^ - 1的狀況,所以我新增程式碼檢查 (第4-5行、第12-15行) [gcc 手冊](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html)對 `__builtin_clz()` 的說明 >Built-in Function: int __builtin_clz (unsigned int x) > Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined. 所以 0 直接使用 `sizeof(uint32_t) * 8` 做檢查 ```clike= for (int try = 0; try < 20; try++) { timec = 0; get_cycles(&timec_high1, &timec_low1); printf("%u:%d \n", 0, clz(0)); assert((sizeof(uint32_t) * 8) == clz(0)); for (uint32_t i = 0; i < 31; i++) { printf("%u:%d \n", 1 << i, clz(1 << i)); for (uint32_t j = (1 << i); j < (1 << (i + 1)); j++) { assert( __builtin_clz (j) == clz(j)); } } printf("%u:%d \n", 1u << 31, clz(1u << 31)); for (uint32_t j = (1u << 31); j < UINT32_MAX; j++) assert(__builtin_clz(j) == clz(j)); assert(__builtin_clz(UINT32_MAX) == clz(UINT32_MAX)); get_cycles_end(&timec_high2, &timec_low2); timec = diff_in_cycles(timec_high1, timec_low1, timec_high2, timec_low2); printf("executiom time : %lu cycles\n", timec); } ``` 重新執行各個程式,沒有 `assert()` 錯誤訊息跳出,確認每個演算法在 32-bit 的範圍內都可以運作成功 ## 解釋個別 clz 運作原理 ### recursive version ### iteration version 原程式碼: ```clike= static inline __attribute((always_inline)) unsigned clz(uint32_t x) { int n = 32, c = 16; do { uint32_t y = x >> c; if (y) { n -= c; x = y; } c >>= 1; } while (c); return (n - x); } ``` 原理:以二分法的方式檢查 先初始假定 clz 值為 32 (n = 32) 每次先檢查前半 bit field 是否為零(平移一半 ( = c) ),分為兩種狀況 * 前半 bit field 不為零: * 更新 n 值,已經確保 n 需減去 x 後半 bit 數 ( = c) * 更新 x 值,接下來只檢查這一半 bit field * 對應的 c 值更新,檢查的範圍剩一半,平移量也更新為一半 * 前半 bit field 為零: * 繼續檢查後半 bit field,檢查的範圍剩一半,平移量( = c)也更新為一半 最後的 n-x 才是 clz 之值,因為最後的 x 之值為 0 或 1 已經沒有辦法對半檢驗( c == 0 ),也就是說迴圈並沒有檢查剩餘一個 bit 的 x 如果為 1 需從 n 值扣除 1 ### byte shift technique 為了方便檢視,就先不遵守 coding style 了 ```clike= static inline __attribute((always_inline)) unsigned clz(uint32_t x) { if (x == 0) return 32; int n = 1; if ((x >> 16) == 0) { n += 16; x <<= 16; } if ((x >> 24) == 0) { n += 8; x <<= 8; } if ((x >> 28) == 0) { n += 4; x <<= 4; } if ((x >> 30) == 0) { n += 2; x <<= 2; } n = n - (x >> 31); return n; } ``` 很像 iteration version,但稍微不一樣 先檢查 x 是否為 0 (x 為 0 在這個演算法沒辦法使用,其實可以使用但將多一個更複雜的 `if` 述句,見底下說明) 是的話直接 return 32 先不管 n 初始化為 1 代表什麼,再來檢驗前半 bit field * 前半 bit field 為零: * 將 n 值加上對應的 bit 數量,已經得知 clz 要加上該數量 * 將 x 平移至前半 bit field ,之後將檢驗該 bit field * 前半 bit field 不為零: * 不做任何事情,之後將檢驗前半 bit field 可以發現是不斷平移 16 、 24 、 28 、 30,也就是不斷檢驗前半 bit field,即使前半 bit field 為 0 ,接下來也將拉升至前半繼續做檢驗,實際上還是在檢驗原本後半的 bit field 本來程式若初始化 n = 0 ,且考慮 x = 0應該寫成以下形式 ```clike if((x >> 31) == 0) { n += 1; x <<= 1;} if((x >> 31) == 0) { n += 1;} return n; ``` 也就是底下為目前剩下的考慮情況 (most significant 的 2 bits) ```clike 00: n = n + 2 01: n = n + 1 10: n = n + 0 11: n = n + 0 ``` 不過如果有 `if (x == 0) return 32;` , `00` 其實是不可能出現的,`00`代表全為 0,但全為 0 已經排除 所以重新整理上面 本來程式若初始化 n = 0 應該寫成以下形式 ```clike if((x >> 31) == 0) { n += 1;} return n; ``` 對應的考慮情況 ```clike 01: n = n + 1 10: n = n + 0 11: n = n + 0 ``` 不過一開始 `n = 1`,就是已經先加 1 了 現在變成 ```clike 01: n = n + 0 10: n = n - 1 11: n = n - 1 ``` `n = n - (x >> 31)`就是正確的數值!而且不用再多一個 `if` 判斷,就像 [Programming Small](https://hackmd.io/s/HkO2ZpIYe) 裡面提到的「原本涉及到分支的陳述,可更換為沒有分支的版本」,沒想到也應用在這裡! ### binary search version ```clike= static inline __attribute((always_inline)) unsigned clz(uint32_t x) { if (x == 0) return 32; int n = 0; if (x <= 0x0000FFFF) { n += 16; x <<= 16; } if (x <= 0x00FFFFFF) { n += 8; x <<= 8; } if (x <= 0x0FFFFFFF) { n += 4; x <<= 4; } if (x <= 0x3FFFFFFF) { n += 2; x <<= 2; } if (x <= 0x7FFFFFFF) { n += 1; x <<= 1; } return n; } ``` 原理本質上和 byte shift technique 相等 另外最後的 `x <<= 1` 是多餘的,因為已經不需要再使用到了(不需要再用來判斷 0 了) ### Harley's algorithm ```clike= static inline __attribute((always_inline)) unsigned clz(uint32_t x) { // CLZ table static uint8_t const Table[] = { 32, 31, 0, 16, 0, 30, 3, 0, 15, 0, 0, 0, 29, 10, 2, 0, 0 , 0, 12, 14, 21, 0, 19, 0, 0, 28, 0, 25, 0, 9, 1, 0, 17, 0, 4, 0, 0, 0, 11, 0, 13, 22, 20, 0, 26, 0, 0, 18, 5 , 0, 0, 23, 0, 27, 0, 6, 0, 24, 7, 0, 8, 0, 0, 0 }; /* Propagate leftmost 1-bit to the right */ x = x | (x >> 1); x = x | (x >> 2); x = x | (x >> 4); x = x | (x >> 8); x = x | (x >> 16); /* x = x * 0x6EB14F9 */ x = (x << 3) - x; /* Multiply by 7. */ x = (x << 8) - x; /* Multiply by 255. */ x = (x << 8) - x; /* Again. */ x = (x << 8) - x; /* Again. */ return Table[(x >> 26)]; } ``` 比起前面其他的演算法來說,Robert Harley's algorithm 未使用到任何的條件述句,分為兩個部份。 * 第一部份:我只看得懂這裡 * 由左至右將遇到第一個 1 後的所有 bit 都變為 1 * 第二部份 * 將 x * 7 * 255 * 255 再右移 26 位,查個表就有答案了?! * 感覺跟之前資訊安全課瞄到的 finite field 、 GF(2^n^) 有關係的樣子? [Hacker's Delight](https://books.google.com.tw/books?id=VicPJYM0I5QC&pg=PA101&lpg=PA101&dq=robert+harley+counting+leading+zero&source=bl&ots=2n6VQWyzWr&sig=eOTAdwGsH7bSd1PuzV-Xc9weQL4&hl=zh-TW&sa=X&ved=0ahUKEwiUquTmtsHSAhWGsJQKHUUUAKsQ6AEILTAC#v=onepage&q=robert%20harley%20counting%20leading%20zero&f=false) 內有提及一些各種 clz 的演算法,也提到了 Harley's algorithm ,但是我看不太懂,也還不曉得使用上的好處 ## 設計實驗 ## clz 應用 * ## 參考資料 [2016秋季班 team 9 Natural merge sort 在特定硬體的加速](https://hackmd.io/s/B1_V03Vlg#) 亮谷學長的[共筆](https://hackmd.io/s/BJBZn6Q6)與[錄影](https://www.youtube.com/watch?v=DRkXFjLfaVg) [](https://books.google.com.tw/books?id=x_-Zsra5IAEC&pg=PA134&dq=counting+leading+zero&hl=zh-TW&sa=X&redir_esc=y#v=onepage&q=counting%20leading%20zero&f=false) [](https://books.google.com.tw/books?id=VicPJYM0I5QC&printsec=frontcover&dq=counting+leading+zero&hl=zh-TW&sa=X&redir_esc=y#v=onepage&q=counting%20leading%20zero&f=false) [](http://www.hackersdelight.org/corres.txt) [](http://embeddedgurus.com/state-space/2014/09/fast-deterministic-and-portable-counting-leading-zeros/) [](https://books.google.com.tw/books?id=VicPJYM0I5QC&pg=PA101&lpg=PA101&dq=robert+harley+counting+leading+zero&source=bl&ots=2n6VQWyzWr&sig=eOTAdwGsH7bSd1PuzV-Xc9weQL4&hl=zh-TW&sa=X&ved=0ahUKEwiUquTmtsHSAhWGsJQKHUUUAKsQ6AEILTAC#v=onepage&q=robert%20harley%20counting%20leading%20zero&f=false)