# 2020q3 Homework2 (quiz2) contributed by < [`OliveLake`](https://github.com/OliveLake/lab0-c.git) > - ==[quiz2 題目](https://hackmd.io/@sysprog/2020-quiz2)== ### Q1: ```cpp= if (str[i] & 0x80) ``` 要判斷是否為有效的 ASCII 字元只需確認第 8 位元( MSB )是否有值。 16 進位的`0x80`= 1000 0000 ,透過 `&`可以判斷 MSB 是否為 1 ,若是則超過範圍( 8 bit )。 在 64 位元的架構下,我們嘗試一次比對 64 位元的資料 (即 word size),程式碼改寫如下: ```cpp= #include <stdbool.h> #include <stddef.h> #include <stdint.h> #include <string.h> bool is_ascii(const char str[], size_t size) { if (size == 0) return false; int i = 0; while ((i + 8) <= size) { uint64_t payload; memcpy(&payload, str + i, 8); if (payload & MMM) return false; i += 8; } while (i < size) { if (str[i] & 0x80) return false; i++; } return true; } ``` `其中 __uint64_t 是 GCC extension,用以表示 64 位元的無號數值。` ```cpp= memcpy(&payload, str + i, 8); ``` 透過 memcpy() 抓取 str 字串的前八個 byte 做比對,每一個 byte 都和一組 `0x80` 做 `&` ,所以 MMM 應選 `0x8080808080808080` 。 MMM = ? - [ ] `(a) 0x0080008000800080` - [ ] `(b) 0x8000800080008000` - [ ] `(c) 0x0808080808080808` - [x] `(d) 0x8080808080808080` - [ ] `(e) 0x8888888888888888` - [ ] `(f) 0xFFFFFFFFFFFFFFFF` ### 延伸問題 ::::info 1.為何特別用到 memcpy 呢? :::: memcpy() 常見的實做是先檢查 memory address 是否 word aligned,如果不是,就先 copy 前幾 bytes 讓目前位址到 word aligned 的地方,然後以 word 而不是 byte 為單位做資料 copy。同樣一個 instruction 可以搬多個 bytes,會比用迴圈每個 byte 逐一 copy 要快。 所以,當資料來源和要寫入的目的記憶體區塊有部份重疊的時候,就不能使用。 例如 strcpy(str, str+1) 這種狀況,就只能每個 byte 逐一 copy 才是安全的。 ::::info 2.將上述技巧應用於「給予一個已知長度的字串,檢測裡頭是否包含有效的英文大小寫字母」 :::: TODO ::::info 3.承 (2),考慮常見的英文標點符號,判斷輸入字串是否為有效的字元集,避免逐一字元比對 :::: TODO ### Q2: ```cpp= uint8_t hexchar2val(uint8_t in) { const uint8_t letter = in & MASK; const uint8_t shift = (letter >> AAA) | (letter >> BBB); return (in + shift) & 0xf; } ``` | char | hex | binary | | -------- | -------- | -------- | | 0 | 0x30 | 0011 0000 | | 9 | 0x39 | 0011 1001 | | A | 0x41 | 0100 0001 | | a | 0x61 | 0110 0001 | | F | 0x46 | 0100 0110 | | f | 0x66 | 0110 0110 | | | 0xf | 0000 1111 | 由 `return (in + shift) & 0xf` 可以知道最後會 return 4 bits ,因此 `MASK` 應為 `0x40` ,由表格可以看到`'F'` 和 `'f'` 、`'A'` 和 `'a'` 的共同位數也是 4 bits ,因此確定 `MASK` 為 `0x40`。 MASK = ? - [ ] `(a) 0x10` - [ ] `(b) 0x20` - [x] `(c) 0x40` - [ ] `(d) 0x60` - [ ] `(e) 0x80` | char | hex | binary | | -------- | -------- | -------- | | MASK | 0x40 | 0100 0000 | 由`'F'` 和 `'f'` 的回傳值都是15(1111)可以知道 `shift` 為 `1001` ,回推 `letter` 需兩次右移3和6。 - [x] `(a) 3` - [ ] `(b) 2` - [ ] `(c) 1` - [ ] `(d) 0` BBB = ? - [ ] `(a) 7` - [x] `(b) 6` - [ ] `(c) 5` - [ ] `(d) 4` ### 延伸問題 ::::info 1.將 hexchar2val 擴充為允許 "0xFF", "0xCAFEBABE", "0x8BADF00D" 等字串輸入,且輸出對應的十進位數值 :::: TODO ### Q3: * 快速除法運算 ```cpp= const uint32_t D = 3; #define M ((uint64_t)(UINT64_C(XXX) / (D) + 1)) /* compute (n mod d) given precomputed M */ uint32_t quickmod(uint32_t n) { uint64_t quotient = ((__uint128_t) M * n) >> 64; return n - quotient * D; } ``` ` __uint128_t 是 GCC extension,用以表示 128 位元的無號數值。` `return n - quotient * D` 符合除法公式: 餘數 = 被除數 - 除數 * 商 ,即 $$ R=n- [\dfrac{n}{3}] * D $$ 又 $$[\dfrac{n}{3}]=n \times \frac{\frac{2 ^ N}{D}}{2 ^ N} $$ `M`應為 ${\frac{2 ^ N}{D}}$ , N = 64 ,`XXX` 最接近${\frac{2 ^ {64}}{D}}$ 的值是 `0xFFFFFFFFFFFFFFF`。 ```cpp= #define M ((uint64_t)(UINT64_C(XXX) / (D) + 1) ``` 需要再 `+1` 是為了再補回忽略的小數。 XXX = ? - [ ] `(a) 0x00F000F000F00080` - [ ] `(b) 0xF000F000F000F000` - [ ] `(c) 0x0F0F0F0F0F0F0F0F` - [ ] `(d) 0xF0F0F0F0F0F0F0F0` - [ ] `(e) 0x8888888888888888` - [x] `(f) 0xFFFFFFFFFFFFFFFF` ### 延伸問題 ::::info 1.由 Facebook 公司所維護的 [ jemalloc ](https://github.com/jemalloc/jemalloc) 是個高效能的記憶體配置器 (memory allocator,即 malloc 和 free 函式對應的實作),特別在多核多執行緒的環境有優異表現,在其原始程式碼 [include/jemalloc/internal/div.h](https://github.com/jemalloc/jemalloc/blob/dev/include/jemalloc/internal/div.h) 和 [src/div.c](https://github.com/jemalloc/jemalloc/blob/dev/src/div.c) 也運用類似的技巧,請閱讀原始程式碼,並抽離快速除法實作為獨立的程式碼,說明運作原理,也該撰寫測試程式,比較透過處理器的整數除法指令及本技巧的效能差距; :::: TODO ### Q4: 承上題,判斷某個數值能否被指定的除數所整除, D 和 M 沿用 ```cpp= bool divisible(uint32_t n) { return n * M <= YYY; } ``` 因為 $$ n*M = ( n\%D )\times 2^{64} $$ 且 $$M = {\frac{2 ^ {64}}{D}+1} , M - 1 = {\frac{2 ^ {64}}{D}} $$ 若不整除則$( n\%D ) = 1$ 或 $2$ $$ n*M > M - 1 $$ 反之整除則 $( n\%D ) = 0$,則 $$ n*M <= M - 1 $$ YYY = ? - [ ] `(a) M + 1` - [ ] `(b) M` - [x] `(c) M-1` - [ ] `(d) (M >> 1)` - [ ] `(e) (M << 1)` ### Q5: 考慮 strlower 函式的作用是將指定的字串 (或部分字串) 的英文字母全改為小寫,其 in-place 的實作如下: ```cpp= #include <ctype.h> #include <stddef.h> /* in-place implementation for converting all characters into lowercase. */ void strlower(char *s, size_t n) { for (size_t j = 0; j < n; j++) { if (s[j] >= 'A' && s[j] <= 'Z') s[j] ^= 1 << 5; else if ((unsigned) s[j] >= '\x7f') /* extended ASCII */ s[j] = tolower(s[j]); } } ``` 在 64 位元處理器架構 (以下針對 x86_64, little endian),我們可引入向量化 (vectorized) 的手段,避免一次比對一個字元,而是直接操作一整個 word (對 x86_64 來說,就是 64 bits,或 8 個位元組)。沿用上述的 strlower 函式,我們用這樣的思路實作向量化的 strlower,程式碼列表如下: ```cpp= #include <ctype.h> #include <stddef.h> #include <stdint.h> #define PACKED_BYTE(b) (((uint64_t)(b) & (0xff)) * 0x0101010101010101u) /* vectorized implementation for in-place strlower */ void strlower_vector(char *s, size_t n) { size_t k = n / 8; for (size_t i = 0; i < k; i++, s += 8) { uint64_t *chunk = (uint64_t *) s; if ((*chunk & PACKED_BYTE(VV1)) == 0) { /* is ASCII? */ uint64_t A = *chunk + PACKED_BYTE(128 - 'A' + VV2); uint64_t Z = *chunk + PACKED_BYTE(128 - 'Z' + VV3); uint64_t mask = ((A ^ Z) & PACKED_BYTE(VV4)) >> 2; *chunk ^= mask; } else strlower(s, 8); } k = n % 8; if (k) strlower(s, k); } ``` VV1 = ? - [ ] `(a) 0x10` - [ ] `(b) 0x20` - [ ] `(c) 0x40` - [ ] `(d) 0x60` - [x] `(e) 0x80` VV2 = ? - [ ] `(a) (-1)` - [x] `(b) 0` - [ ] `(c) 1` VV3 = ? - [x] `(a) (-1)` - [ ] `(b) 0` - [ ] `(c) 1` VV4 = ? - [ ] `(a) 0x10` - [ ] `(b) 0x20` - [ ] `(c) 0x40` - [ ] `(d) 0x60` - [x] `(e) 0x80` ::::info 1.將前述測試程式 main 函式的 char str[] 更換為 char *str,會發生什麼事?請參閱 C 語言規格書,說明 string literal 的行為 :::: TODO ### Q6: 考慮以下程式碼為 LeetCode [137. Single Number II](https://leetcode.com/problems/single-number-ii/) 的題解 ```cpp= int singleNumber(int *nums, int numsSize) { int lower = 0, higher = 0; for (int i = 0; i < numsSize; i++) { lower ^= nums[i]; lower &= KKK; higher ^= nums[i]; higher &= JJJ; } return lower; } ``` KKK = ? - [ ] `(a) higher` - [ ] `(b) lower` - [x] `(c) ~higher` - [ ] `(d) ~lower` - [ ] `(e) 0xFFFFFFFF` JJJ = ? - [ ] `(a) higher` - [ ] `(b) lower` - [ ] `(c) ~higher` - [x] `(d) ~lower` - [ ] `(e) 0xFFFFFFFF` ::::info 1.請撰寫不同上述的程式碼,並確保在 LeetCode 137. Single Number II 執行結果正確且不超時 :::: TODO ::::info 2.延伸題意,將重複 3 次改為改為重複 4 次或 5 次,並找出可容易推廣到多次的程式碼 :::: TODO