---
# System prepended metadata

title: 2020q3 Homework2 (quiz2)
tags: [sysprog2020]

---

# 2020q3 Homework2 (quiz2)
contributed by <`joe-U16`>
###### tags: `sysprog2020`

## 測驗 `1`
```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;
```
### Q1
:::info
解釋上述程式的運作原理，特別是為何用到 memcpy 呢？提示: 與 data alignment 有關，詳閱 [C 語言：記憶體管理、對齊及硬體特性](https://hackmd.io/@sysprog/c-memory)
:::

* MMM=0x8080808080808080
	* ASCII 定義了0-127 共128個字元，若 payload 這個字元的第 8個 bit 有值，則會讓 `(payload & MMM)` 為 1，進而 return false。 
* 在 x86_64 中 unsigned long long 一次對齊是 8 bytes
	* 使用memcpy 將 8 個 bytes 大小的字元 payload 這個變數內
### Q2
:::info
將上述技巧應用於「給予一個已知長度的字串，檢測裡頭是否包含有效的英文大小寫字母」
:::
### Q3
:::info
承 (2)，考慮常見的英文標點符號，判斷輸入字串是否為有效的字元集，避免逐一字元比對
> 提示: Intel SSE 4.2 加入 STTNI (String and Text New Instructions) 指令，可加速字串比對，[詳見: Schema Validation with Intel® Streaming SIMD Extensions 4](https://software.intel.com/content/www/us/en/develop/articles/schema-validation-with-intel-streaming-simd-extensions-4-intel-sse4.html)
:::

## 測驗 `2`

```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;
}
```

### Q1
:::info
 解釋上述程式的運作原理;
:::

* `第5行` 跟 `0xf` 做 `&` ，因為要表達的值只有0-15
* 可以發現做 `第5行` 的話 `0-9` 不用處理，就可以得到答案了。
* `f` = 0110 0110
* `F` = 0100 0110
* `9` = 0011 1001
* `9` 不需要shift，所以MASK 是為了讓 `0-9` 跟 `a-f` 做出區別，
	* MASK=0x40， `0-9` 不會有 letter ，`a-f` 有letter
* 如果只看 `8` `4` `2` `1` ，4個bit， `f` 為 `0110` 所以想要讓他變 `15` ，要再讓他加 9，所以 AAA=3 BBB=6 可以讓 shift=9
* 成功了
* Ans:
	* MASK=0x40
	* AAA=3
	* BBB=6

### Q2
:::info
將 hexchar2val 擴充為允許 "0xFF", "0xCAFEBABE", "0x8BADF00D" 等字串輸入，且輸出對應的十進位數值
:::






## 測驗 `3`

```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;
}
```

### Q1
:::info
解釋上述程式的原理;
:::
* $M$ 即為 ${2^N \over d}$
* ${n \over d} = n * {{2^N \over d} \over 2^N}$
* `quodient` 為 ${n \over d}$ 的商數
* 最後n - `quodient` * d 即為餘數。
* Ans:
	* `XXX=0xFFFFFFFFFFFFFFFF`

### Q2
:::info
由 Facebook 公司所維護的 jemalloc 是個高效能的記憶體配置器 (memory allocator，即 malloc 和 free 函式對應的實作)，特別在多核多執行緒的環境有優異表現，在其原始程式碼 include/jemalloc/internal/div.h 和 src/div.c 也運用類似的技巧，請閱讀原始程式碼，並抽離快速除法實作為獨立的程式碼，說明運作原理，也該撰寫測試程式，比較透過處理器的整數除法指令及本技巧的效能差距;
:::

## 測驗 `4`
判斷某個數值能否被指定的除數所整除，在 D 和 M 都沿用的狀況下，程式碼如下:
```cpp=
bool divisible(uint32_t n)
{
    return n * M <= YYY;
}
```

$n * M$ 即為 $n * ({{2^N-1} \over d}+1)$

* `n * M = n * ((0xFFFFFFFFFFFFFFFF / d) + 1)` => `(n * (0xFFFFFFFFFFFFFFFF / d)) + (n * 1)` 
* 如果 n 可被 d 整除，則 $n*M$ 會溢位得到很小的數
* 運用上述規則來判斷 n 是否可整除 d
* 考慮 `n=1` 且 `d=2` ，此條件下 不可整除 n ，函式該 return `0` 
* 推導
	* $n * ({{2^N-1} \over d}+1)\lt=  YYY$
	* 代入 n=1 d=2
	* $1 * ({{2^N-1} \over 2}+1)\lt=  YYY$
	* $M <= YYY$ 需return `0`
	* 可推知YYY為選項 M-1 或 (M >> 1)
	* 找 M>>1的反例
		* 如果 n 不可整除 d 則 YYY 需小於 M， (M >> 1) 可達成
		* 如果 n 可整除 d 則 YYY 須大於等於 M
		* 找不到反例
	* M-1 和 (M>>1) 都對

## 測驗 `5`
```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=0x80  VV2=0  VV3=(-1)  VV4=0x80
```
### Q1
:::info
解釋上述程式的原理
:::
:::success
以下討論為不使用PACKED_BYTE()這個 macro ，使用一個字元8 bits 的情況，可較直觀的理解程式碼的目的。
:::
* VV1
	* PACKED_BYTE() 這個 macro 功能是只取 input 前面 8 個 bits ，並將得到的資料重複擴展到 64 bits
	* ASCII 定義了0-127 共128個字元 透過 ==#13==` (*chunk & 1000 0000(b)) == 0` 可判斷是否為 ASCII ，若是才繼續做，不是的話就做extended ASCII
	* `VV1 = 0x80`  
* ==#16== mask
* ==#17== 如果`(*chunk ^= ' ')` ，可讓\*chunk 這個字元大小寫互換，而`' '`即為`0b00100000`
	* 若要讓mask 符合此值，==#16==`(A ^ Z)` 的第 8 bit 需為 1
	* 再與`0x80`做`&`，可得`0b10000000`
	* 再右移2格可得`0b00100000`，即為`' '`
	* 可知 `VV4=0x80`
* `A ^ Z` 的第8 bit 為 1
	* `A = (*chunk) + (128 - 65 + 0)`，若 \*chunk 大於等於65，則A可得大於128的值
	* `B = (*chunk) + (128 - 90 + (-1))`，若 \*chunk 小於等於`90`，則B可得小於128的值
	* 可知 `A^Z` 目的為判斷 \*chunk 是否為字元 A-Z ，若是，才需大小寫轉換。

### Q2
:::info
將前述測試程式 main 函式的 char str[] 更換為 char *str，會發生什麼事？請參閱 C 語言規格書，說明 string literal 的行為
:::

* char str[] 更換為 char *str
	* 會發生 segmentation fault(core dumped)
	* 在[你所不知道的C語言：指標篇](https://hackmd.io/@sysprog/c-prog/%2Fs%2FHyBPr9WGl)中有提到：
	    * definition/statement, 如 char x[10] →不能變更為 pointer 的形式
* 請參閱 C 語言規格書，說明 string literal 的行為
	* 是一種字串常值的型態

## 測驗 `6`

```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=~higher  JJJ=~lower
```
### Q1
:::info
解釋前述程序的原理;
:::

* 取一個例子 [2, 2, 2, 3]
    * 第一個迴圈做完後 `lower=2` `higher=0`
    * 第二個         `lower=0` `higher=2`
    * 第三個迴圈  `lower=0` `higher=0` 像是一切重新計算
    * 第四個迴圈 `lower=3` `higher=0` higher = return lower
    * 不管排列組合怎麼排，都可以讓數字做三次後就歸零

### Q2
:::info
請撰寫不同上述的程式碼，並確保在[LeetCode 137 Single Number](https://leetcode.com/problems/single-number-ii/) 可成功執行
:::

### Q3
:::info
延伸題意，將重複3次改為重複重複4次或5次，並發現可容易推廣到多次的程式碼;
:::

如果要重複次數為偶數次的話，使用 `Xor` ，利用相同數字 `Xor` 後會為0的特性，即可得解：
```cpp=
int singleNumber(int *nums, int numsSize)
{
    int tmp = 0;
    for (int i = 0; i < numsSize; i++) {
        tmp ^= nums[i];
    }
    return tmp;
}
```


