# 2016q3 Homework1 (clz) contributed by <`CheHsuan`> ## 目標 閱讀 重新理解數值 裡頭關於 count leading zero (clz) 的描述,設計實驗,比較 32-bit 數值對以下實做的效能差異: * recursive version * iteration version * binary search technique * byte-shift version * Harley’s algorithm ## 程式碼分析 ```c uint8_t clz(uint32_t x) { /* shift upper half down, rest is filled up with 0s */ uint16_t upper = (x >> 16); // mask upper half away uint16_t lower = (x & 0xFFFF); return upper ? clz(upper) : 16 + clz(lower); } ``` 老師給的版本有一個問題,就是沒有中止條件使程式跳出recursive call,所以必須改寫一下 ```c uint8_t clz_binary_recursive(uint32_t x, int shift) { if (x == 0) return 32; if (shift == 0) return 0; if (x <= (0xFFFFFFFF >> shift)) return shift + clz_binary_recursive(x << shift, shift / 2); return clz_binary_recursive(x, shift / 2); } ``` 這個寫法是類似binary search的方式,理論上時間複雜度為O(logN) ## Recursive Version ```c uint8_t clz_recursive(uint32_t x) { return x ? clz_recursive(x>>1) - 1 : 32; } ``` 但這個版本的時間複雜度是O(N),而且比iteration版本的clz多出了function call的成本 ## Iteration Version ```c uint8_t clz_iteration(uint32_t x) { int count = 0; while(x != 0) { x = x >> 1; count++; } return 32 - count; } ``` 時間複雜度為O(N) ## Binary Search Technique 此版本我是參考老師[重新理解數值](https://hackmd.io/s/SkKZBXZT#count-leading-zero)裡面的作法,從這個算法來看,時間複雜度為O(logN) ## Byte-shift Version 一樣參考[重新理解數值](https://hackmd.io/s/SkKZBXZT#count-leading-zero),這版本的算法的時間複雜度和binary search technique一樣為時間複雜度為O(logN),只少了一次if比較,不過應該不會相差太多 ## Harley's Algorithm ```c static uint8_t Table[64] = { 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}; ``` 修改table內數字,OK成功 ## 時間量測方法 參考compute-pi作法,使用`clock_gettime()`函式 man一下 `int clock_gettime(clockid_t clk_id, struct timespec *tp);` :::info The clk_id argument is the identifier of the particular clock on which to act. A clock may be system-wide and hence visible for all processes, or per-process if it measures time only within a single process. ::: ### CLOCK_REALTIME 在manual裡面指出,clock_gettime可以指定量測system-wide time,也就是wall clock time,因此會因機器假如同時運行其他程式的話,會影響到時間量測的準確度,所以我們不考量使用這方法 ### CLOCK_MONOTONIC和CLOCK_MONOTONIC_RAW 利用系統的jiffies來計算時間,當發生time interrupt時,便把紀錄+1 ### CLOCK_PROCESS_CPUTIME_ID 計算整個process在kernel space和user space所花費的時間,假如是多執行緒,會將每個thread所花的時間加起來 ### CLOCK_THREAD_CPUTIME_ID 計算單個thread在kernel space和user space所花費的時間 ### timespec結構 ```c struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }; ``` ## 實驗數據 這次的實驗環境是計算0X0~0XFFFF的clz,每個方法都跑100次,數據如下 ![](https://i.imgur.com/Ero1w5D.png) 從圖中可以看出兩件事情 1. 執行時間會有振盪的現象 2. iteration和recursive雖然都是O(N),但iteration因為少了call function的overhead,所以執行時間平均來說較recursive少 再把跑的次數拉大一點來看,一樣是0X0~0XFFFF各跑5000次 ![](https://i.imgur.com/dY9VWJV.png) 針對32 bits unsigned integer的極值(0~4294967296)各跑1次 ![](https://i.imgur.com/SoIitho.png) 演算法的重要性!recursive的版本花到487.10秒,也就是八分鐘,夠吃完一碗泡麵了 :smile: ![](https://i.imgur.com/fGAj3Vb.png) ||recursive|binary recursive|iteration|byte-shift|binary search technique | :------ | ------: | ------: |------: | ------: | :------: | |0| 486.492851| 110.374512|264.005961| 22.064742| 21.832292 |1| 485.631612| 110.86067| 263.157863| 22.039492| 21.640222 OK,把recursive的版本用O(N)和O(logN)的方法來比較一下,執行時間相差了約4倍(486:110) 而同樣都是O(logN)的算法,recusive function call和one function call with multiple if-else的方法來比較,時間也差了大概5倍(110:22) 加入Harley ![](https://i.imgur.com/uFDyH1N.png) 最後總測試 ![](https://i.imgur.com/vBMNiGr.png) ## 應用實例 [WIP]