# 2017q1 Homework1(clz) contributed by < `baimao8437` > ### Reviewed by `jack81306` * 在設計實驗的部分,可以多設計幾個範圍來測試不同方法的效能 * 把不同的方法弄成圖表來比較 * 弄成脈衝圖可以更明顯地觀察到變化 * 可以把應用實例的超連結直接放上去 ### 花費時間 ~3/3~ ~-~ ~3/4~ * 讀書:6 hr * 文件:4 hr * coding: 4 hr * ~~看著發呆:1 hr~~ ## 目標設定 * 習慣 linux 系統基本操作 * 熟練 git 版本控管操作 * 繪圖工具 gnuplot * 提升對 binary 、 bitwise 熟練度 * 減少發呆時間... ## 前置作業 一開始 make 發現沒有 git hook的兩個檔案 從之前的作業複製過來 就可以 make 了 並將在 compute-pi 學習到的 makefile 簡化法 套用這個作業 原本也想學習 compute-pi 的 test_time.c 將所有算法寫在一起 再用 define 去分別執行 但在這裡 程式的關係更為複雜 所以先不要亂動好了~ ## 開發環境 ``` baimao@baimao-Aspire-V5-573G:~$ lscpu Architecture: x86_64 CPU 作業模式: 32-bit, 64-bit Byte Order: Little Endian CPU(s): 4 On-line CPU(s) list: 0-3 每核心執行緒數:2 每通訊端核心數:2 Socket(s): 1 NUMA 節點: 1 供應商識別號: GenuineIntel CPU 家族: 6 型號: 69 Model name: Intel(R) Core(TM) i5-4200U CPU @ 1.60GHz 製程: 1 CPU MHz: 1711.242 CPU max MHz: 2600.0000 CPU min MHz: 800.0000 BogoMIPS: 4589.38 虛擬: VT-x L1d 快取: 32K L1i 快取: 32K L2 快取: 256K L3 快取: 3072K NUMA node0 CPU(s): 0-3 ``` ## CLZ ### recursive version ```c= static const int mask[]={0,8,12,14}; static const int magic[]={2,1,0,0}; unsigned clz2(uint32_t x,int c) { if (!x && !c) return 32; uint32_t upper = (x >> (16 >> c)); uint32_t lower = (x & (0xFFFF>>mask[c])); if (c == 3) return upper ? magic[upper] : 2 + magic[lower]; return upper ? clz2(upper, c + 1) : (16 >> (c)) + clz2(lower, c + 1); } ``` >先看懂 return 跟 ternary(?:) 的混合使用 ``` if (a >=0) return a; -----> return a>=0 ? a : -a else 這是判斷 return -a; 後面才是回傳值 ``` 運作原理: c 初始值為0 代表所在層數(32-16-8-4-2) 運作到 c=3 時會開始回傳加總結果 magic 主要做的是在剩下兩個 bits 以後(c\==3)判斷有幾個 0 x 一進入後判斷都是0的話就回傳32 再來分為 upper & lower 兩半 當 c!=3 檢查 upper 是否為0 不是0就代 upper 進下層 是0 就代 lower 進下層且==回傳值會加上確定的 0 數 (16>>c)== 當 c\==3 檢查 upper 是否為0 此時 upper 剩下2bits 不是0 就回傳 magic[upper] 是0就回傳 2(upper=00) + magic[lower] ==magic代表的就是最後有幾個0了 接著回傳 加上之前確定的0數 就是答案== ### iteration version ```c= 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); } ``` 運作原理: y 的性質跟 recursive 的 upper 一樣 先判斷 upper 是否為 0 不為 0 ==就先把 n 扣掉已知不重要 bits 的個數== upper 範圍縮小一半再跑一次迴圈 等於 0 就往 lower 的地方縮小範圍去跑一次迴圈 直到 y 都是 0 了 等 c 跑完跳出迴圈 最後回傳 n - x (x皆為1 n為左邊數來第幾個不為零的數) ### binary search technique 一半一半 ```c= 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; } ``` 運作原理: 每次比一半 若 x <= 0x0000ffff 表示==確定有前面 16 位是 0== 就==把 x 往左 shift 再比一半== 依此類推 只要確定前面有幾個 0 就好 ### byte-shift version 直接做位移 ```c= 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; } ``` 運作原理: 跟 binary 類似的原理 每次砍半 利用==直接位移的方式==來檢查前面有幾個確定為 0 有找到確定為 0 時 就要==把 x 往左 shift 檢查 lower ==的前面有幾個 0 最後看==第一位是否為 0 (x >> 31)== 扣掉後即為答案 ### Harley's algorithm 算第幾個位置是 leading 1 ```c= unsigned clz(uint32_t x) { static uint8_t const Table[] = { 0xFF, 0, 0xFF, 15, 0xFF, 1, 28, 0xFF, 16, 0xFF, 0xFF, 0xFF, 2, 21, 29, 0xFF, 0xFF, 0xFF, 19, 17, 10, 0xFF, 12, 0xFF, 0xFF, 3, 0xFF, 6, 0xFF, 22, 30, 0xFF, 14, 0xFF, 27, 0xFF, 0xFF, 0xFF, 20, 0xFF, 18, 9, 11, 0xFF, 5, 0xFF, 0xFF, 13, 26, 0xFF, 0xFF, 8, 0xFF, 4, 0xFF, 25, 0xFF, 7, 24, 0xFF, 23, 0xFF, 31, 0xFF, }; /* 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]; } ``` 運作原理: 主要操作分為兩部份 第一部份較簡單 是在將第一個(左到右)不為0的後面 全部都變1 直接看數字就懂 至於這樣做的理由 等等解釋 ``` input: 00000000 00000000 00000000 10000000 stage1: 00000000 00000000 00000000 10000000 00000000 00000000 00000000 11000000 00000000 00000000 00000000 11110000 00000000 00000000 00000000 11111111 00000000 00000000 00000000 11111111 00000000 00000000 00000000 11111111 ``` 第2部份就比較神奇了 我看不出這些操作是什麼道理 看一下數據 ``` 延續上方 stage2: 00000000 00000000 00000110 11111001 00000000 00000110 11110010 00000111 00000110 11101011 00010100 11111001 11100100 00101001 11100100 00000111 ^^^^^^ index: 111001 <--- x >> 26 ``` 只取==最前面6個數字當 index== 然後去找 Table[index] 得到的數字是 右邊數來第幾個是 leading 1 (0 代表第一位) 神奇的來了 第2部份的操作到底再幹嘛呢 我不想理解了直接看數據 我將 $2^0$~$2^{31}$ 當作 input 去看程式輸出 結果: ``` input =>Table[index]=> result 1 => Table[ 1] => 0 2 => Table[ 5] => 1 4 => Table[12] => 2 8 => Table[25] => 3 16 => Table[53] => 4 32 => Table[44] => 5 64 => Table[27] => 6 128 => Table[57] => 7 256 => Table[51] => 8 512 => Table[41] => 9 1024 => Table[20] => 10 2048 => Table[42] => 11 4096 => Table[22] => 12 8192 => Table[47] => 13 16384 => Table[32] => 14 32768 => Table[ 3] => 15 65536 => Table[ 8] => 16 131072 => Table[19] => 17 262144 => Table[40] => 18 524288 => Table[18] => 19 1048576 => Table[38] => 20 2097152 => Table[13] => 21 4194304 => Table[29] => 22 8388608 => Table[60] => 23 16777216 => Table[58] => 24 33554432 => Table[55] => 25 67108864 => Table[48] => 26 134217728 => Table[34] => 27 268435456 => Table[ 6] => 28 536870912 => Table[14] => 29 1073741824 => Table[30] => 30 2147483648 => Table[62] => 31 ``` 太神奇了傑克~ 32 個 input 剛好對應了不同的 index 那我合理的將這個算法理解為一個 **Hash** 那兩個操作部份的意義也很明顯了 第1部份是為了同化結果 e.g. 1001 & 1100 經過 stage 1 都會變成 1111 那第2部份的操作就是他對 stage 1 ouput 的 **Hash Function** 愈複雜的函數能將資料分的愈均勻 Table 中就是各 index 對應的輸出結果 ==右邊數來第幾個是leading 1==(0是第一位) 那 hash 就是以空間換取時間的作法 所以花費時間可以很短 但空間花費相對大 ### 原始效能 gnuplot ![](https://i.imgur.com/2QBpsF2.png) 可以看到 如影片中所說 byte & binary 所用 cycles 最少 harley 也相對平穩 ### 偵錯 使用原本寫好的工具 在 ```make run``` 後面加上 ```PROFILE=1``` 就會進入偵錯工具 順利用所有 method 跑完所有 32-bit 數值沒有錯誤 ## 設計benchmark suite 我覺得原本的 makefile 方式可以帶來非常大的便利性 只要加上不同的 define 就可以進行不同的操作 define 的用法又比上一個作業高上一層 我還沒想到有什麼更快更便利的方法來設計 但還是自己寫了一個能夠輸出不同位錯誤個數的測試方式 輸入```make generrcsv```就會產生64*5表格 格式為 ``` 1bit_errcount , 1bit_time ,2bits_errcont , 2bits_time.... (next method)... ``` 但是...還在思考圖像化要怎麼表示比較好... ## 應用 clz 的場合 * 在浮點數除法的優化 * 乘法的溢位偵測 * 計算以2為底數的對數值 其實還在理解如何應用... ## 參考資料: * [clz 應用1](https://embedded2015.hackpad.com/ep/pad/static/PLZhxd8JpQ4) * [clz 應用2](https://hackmd.io/s/S153n-G1g) * [Makefile 字串函式](http://deanjai.blogspot.tw/2008/02/subst-subst-subst-eeeefeet-on-street.html)