# 2017q1 Homework1 (clz)
contributed by < `laochanlam` >
###### tags: `laochanlam`
### Reviewed by you74674
* 這份文件感覺蠻多東西,也有效能測試,這部分感覺還不錯。
* 一個commit裡面感覺太多東西
* 應該分次,或至少把code修正與其他檔案(例如.gitignore等等)的commit分開。
* 很多地方只是修正空白的,看起來很雜,不容易閱讀,應該也要分開。
* 第一個commit有清楚解釋bug。
* 第二個commit中的redundant code不清楚。
## 開發環境
- Ubuntu 16.10 (64bit)
```
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 2
Core(s) per socket: 2
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 61
Model name: Intel(R) Core(TM) i7-5500U CPU @ 2.40GHz
Stepping: 4
CPU MHz: 2400.878
CPU max MHz: 3000.0000
CPU min MHz: 500.0000
BogoMIPS: 4788.90
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 4096K
NUMA node0 CPU(s): 0-3
```
## 相關連結
- [2017 年春季作業說明](https://hackmd.io/s/Bk-1zqIte#)
- [2017q1 Homework1 (作業區)](https://hackmd.io/MYJjEYGZITgWgOwCMAmw4BYYwAxyQQKxwBmwGOkAHMDeEgGxA===?view)
- [B04: clz 作業要求](https://hackmd.io/s/ry1u0uDFg)
- [clz Github連結 ( laochanlam ) ](https://github.com/laochanlam/clz-tests)
- [作業解說 video](https://www.youtube.com/watch?v=DRkXFjLfaVg)
- [課程進度與開放資源 Wiki](http://wiki.csie.ncku.edu.tw/sysprog/schedule)
## clz 開發紀錄
先來理解一下各版本 clz 的運作原理。
### iteration version
```C
#include "clz.h"
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);
}
```
透過 c 實現不斷切半,向左逼近最近的 y 不全為 0 的位置,最後計算並返回 clz 值。
### recursive version
```C
#include "clz2.h"
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);
}
```
透過遞迴的方法縮小範圍,並用 const array 直接查表取代運算,求出 clz。
### binary search technique
```C
#include "clz.h"
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;
}
```
知道 x 落在哪一個區間內,再縮小範圍,求出 clz。
### byte-shift version
```C
#include "clz.h"
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;
}
```
與 binary search technique 同理,縮小範圍,求出 clz。
### Harley’s algorithm
```C
#include "clz.h"
static inline __attribute((always_inline))
unsigned clz(uint32_t x)
{
// CTZ table
#ifdef CTZ
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,
};
// CLZ table
#else
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
};
#endif
/* 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)];
}
```
這個不太能理解,應該是某種神秘的演算法,晚點再來讀資料。
### 驗証
在 min 到 max 次的 clz 計算中加上 ```assert( __builtin_clz (i) == clz(i));``` 以內建函式計算出來的 clz 對比各版本的 clz ,並沒有出現錯誤,可証明其準確性。
<br>
### _Generic
在使用 C11 的 _Generic 改寫程式時,發現有以下兩個問題。
1. C11 不能認出 get_cycles 中寫的 asm,即可能不支援 rdtscp ,或是有別的語法,所以把原來的語言規範從 C99 改成 C11 後,不能繼續使用 **get_cycles** 及 **get_cycles_end**。
>> 在 `CFLAGS` 指定 `-std=gnu11` [name=jserv]
2. C11 的 _Generic 沒有直接支援多參數的輸入,若要對程式進行改寫,可參照以下[方法](http://stackoverflow.com/questions/17302913/generics-for-multiparameter-c-functions-in-c11),而且暫時正在想方法解決出對於**參數數量不同的幾個函式**進行 _Generic 的使用。
<br>
接下來看看 ```make plot``` 畫出的圖像
![Imgur](http://i.imgur.com/pMplRGI.png)
範圍從 67100000 ~ 7611800 ,由圖可見 resursive 版本所執行的時間最慢,而 binary search version 及 byte-shift version 則是最快。
---
## 補充知識
- [x] taskset
- [ ] Harley’s algorithm
- [ ] jitter
- [ ] _Generic
---
## taskset
使用 taskset 指令可實現充分利用多核 cpu , 把 cpu 使用率均衡化。
>-p, 設定一個已存在的pid,而不是重新開啟一個新任務
-c, 指定一個處理,可以指定多個,以逗號分隔,也可指定範圍,如:2,4,5,6-8。
:::danger
不是「使用率均勻化」,請見 [taskset](http://www.tutorialspoint.com/unix_commands/taskset.htm),注意 CPU affinity --jserv
:::
<br>
例子:
```
$ taskset -c 1,2,3 ./raytracing
```
指定 raytracing 運行在 cpu 1 , 2 , 3 上。
#### 參考及引用資料
[Linux下調節CPU使用的幾種方法](https://read01.com/7Dja4m.html)
---
## Harley’s algorithm
#### 參考及引用資料
[]()
---
## Jitter
#### 參考及引用資料
[]()
---
## _Generic
一種以 macro 作為基礎實現在 C11 上類似於 C++ template 的一種程式語法。
範例程式碼:
```C
#include <stdio.h>
void funci(int x) { printf("func value = %d\n", x); }
void funcc(char c) { printf("func char = %c\n", c); }
void funcdef(double v) { printf("Def func's value = %lf\n", v); }
#define func(X) \
_Generic((X), \
int: funci, char: funcc, default: funcdef \
)(X)
int main() {
func(1);
func('a');
func(1.3);
return 0;
}
```
### 待研究解決的問題
- [ ] 研究 rdtscp 是否能執行在 C11
- [ ] 研究如何更有彈性地使用 _Generic
#### 參考及引用資料
[Generics for multiparameter C functions in C11](http://stackoverflow.com/questions/17302913/generics-for-multiparameter-c-functions-in-c11)
[你所不知道的 C 語言:前置處理器應用篇](https://hackmd.io/s/S1maxCXMl#)
[Rob's Programming Blog](http://www.robertgamble.net/2012/01/c11-generic-selections.html)
---