2021q1 Homework2 (quiz2)
contributed by < ccs100203
>
第 2 週測驗題
測驗 1
以下是針對 doubly-linked list 的 merge sort 實作:
解釋程式原理
參考 Merge sort
程式會藉由 get_middle
取得該 list 的中間點,再透過 list_cut_position
做切割,然後重複的遞迴呼叫。當所有節點都分開後會經由 list_merge
將其排序以及結合。
get_middle
此函式的目的是找出 list 的中間點
下面的圖表只繪製出 next
指標
- 首先,會分別用
fast
、slow
指標指向 list 的前兩個 node
- 再來進入了
list_for_each
所定義的迴圈內,一開始的賦值階段會先將 slow
指向下一個節點
- 在 if 的判斷通過之前,迴圈會讓
slow
不斷指向下一個節點,而 fast
則會指向下下個節點
- 此時,
fast->next->next == list
的判斷式會成立
可以看出 slow
會位於 list 中間的節點,所以藉著 slow
就可以將 A、B、C 與 D、E、F、G 分開。
list_cut_position
此函式用來將一條 list 從特定的點切分成兩條 list。
他會將 head_from
之後一直到 node
之間的節點接到 head_to
。
舉例來說:
最終情況會變成下列兩條 list
測驗 2
考慮函式 func 接受一個 16 位元無號整數 ,並回傳小於或等於 的 power-of-2 (漢字可寫作為 2 的冪)
- 先考慮一個數字如何轉換為 power of 2,以一個隨意的 16 位元無號整數來說,只要保留最高位的 1 並將其他都轉換為 0 即可。
- 所以先將比最高位還低的位元全部轉換為 1,這樣就可以產生出 的數字,此時只需要將這個數字 + 1,再將他往右位移,就可以得到 power of 2。
- 以 1025 為例,可以看到透過前面的幾行會將較低位元的 bit 全數轉換為 1,再透過第八行將他轉換為 power of 2。
| after
execution | 15
MSB | 14~11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0
LSB |
| - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
| Line 2 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| Line 3 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| Line 4 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| Line 5 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 |
| Line 6 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| Line 8 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
查閱 Linux 核心原始程式碼 power of 2
根據 log2.h
is_power_of_2
- 已知如果一個數字只有任一且唯一一個 bit 為 1,其餘為 0,那麼他就屬於 power of 2。
(n & (n - 1)
的操作,其實就是將最低位的 bit 設為 0。
假設 n
為 , n - 1
就是 ,此時做 and 運算就可以將最底位的 bit 設為 0。
也藉由這個特性,跟上述已知道的條件,所以在做完 (n & (n - 1)
後,若數字變為 0,則代表其為 power of 2。
__roundup_pow_of_two
和 __rounddown_pow_of_two
解析
__roundup_pow_of_two
是為了將數字無條件進位到比較大的 power of 2,反之則為 __rounddown_pow_of_two
。
- 以 舉例:
__roundup_pow_of_two(24)
應得到 32
__rounddown_pow_of_two(24)
應得到 16
觀察以下程式碼,fls_long
的作用是 find leading set,也就是找出 bit 中最高位的 1。
- 已知
fls_long(24)
會得到 5
__roundup_pow_of_two
只要再藉由 1U << 5
就能得到 32,那為什麼需要 n-1
呢?
假設今天不是 24,而是 32 這種屬於 power of 2 的數字時,程式是不需要做進位的,但是直接做 fls_long(32)
會得到 6,代表程式在不需要進位時還是會進位。所以需要藉由 n-1
去避免掉程式多做了進位的情況。
__rounddown_pow_of_two
由於是要無條件捨去,所以可以很直觀的將 fls_long(n)
的結果 - 1,也就是只保留了原先數字中最高位為 1 的 bit。
如何 find leading set
由於上述的程式用到了 find leading set 的功能,所以來解釋他的做法。首先可以看到 fls_long
只是藉由不同的 type size 去呼叫不同的 fls。
fls_long
這邊只擷取了某一部分的情況,可以看到 find leading set 是藉由 number of bits 減 count leading zero 做出來的,而 __kernel_ctlz
又會藉 GCC Built in function 裡的 __builtin_clzl
去實作。
透過這些函式的串聯,就可以知道 fls_long
的功用與作法。
fls && fls64
__kernel_ctlz(x)
__attribute__((const))
用處
參考 Declaring Attributes of Functions
Basically this is just slightly more strict class than the pure attribute below, since function is not allowed to read global memory.
Note that a function that has pointer arguments and examines the data pointed to must not be declared const. Likewise, a function that calls a non-const function usually must not be const. It does not make sense for a const function to return void.
規定 function 不能夠含有指標相關的參數以及調動到 global memory,同時也要求其必須有 void 以外的回傳值。
換言之,__attribute__((const))
讓 C function (函式) 限縮到數學意義上的 function (函數),這也是為何中文翻譯應該區分「函式」和「函數」
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
jserv
測驗 3
以下是 bitcpy
實作,允許開發者對指定的記憶體區域,逐 bit 進行資料複製:
(避免篇幅過長故隱藏)
透過 read_lhs
與 read_rhs
可以知道資料所需的偏移量。而 write 也是使用了相同的手段。
而 _read / 8
可以得知要從第幾個 byte 開始讀取。
限制每次最多處理 8 bits。
並且藉由 bitsize
與 read_rhs
的比較去判斷此次運算是否需要跨越到下一個 byte。
如果需要的話代表 data
需要加入下一個 byte 內最高位開始的 read_rhs
個 bit。
而 read_mask
與 write_mask
的用處就在於保證程式只更改 / 更新到所需要的 bits。
如果需要寫入最低位的 3 個 bit,則會產生下列的 mask:
再來利用 *dest = (*dest & mask) | (data >> write_lhs);
(*dest & mask)
可以保留不須更改的高位 5 個 bits。
(data >> write_lhs)
則是用來取出需要寫入的 bits。
將兩者做 bitwise or 運算就可以完成預期中寫入的動作。
- 程式碼寫入實作說明
如果不需要跨位元組寫入,透過 mask |= write_mask[write_lhs + bitsize];
會將需要寫入的 bits 設為 0,而其餘設為 1,就可以透過下一行的操作在不更動其餘資料的狀況下將資料填入。
如果需要跨位元組寫入,第 60 行的會先寫入第一個 byte,利用 mask
將其操作限制在最右邊幾個所需的 bits。而下面兩行則是利用 write_mask
將需要寫入的幾個高位元的 bit 設為 0,其餘不變的則為 0,藉此完成寫入的操作。
TODO 研究 linux kernel bitmaps
測驗 4
參考 String interning
In computer science, string interning is a method of storing only one copy of each distinct string value, which must be immutable. Interning strings makes some string processing tasks more time- or space-efficient at the cost of requiring more time when the string is created or interned.
以及 Immutable object
In object-oriented and functional programming, an immutable object (unchangeable object) is an object whose state cannot be modified after it is created.
String interning 是將出現多處的字串,只保存在單一的儲存空間,存取時直接讀取 reference,藉此節省運算與儲存成本。
以下是 string interning 在 C 語言中的簡易實作:
(避免篇幅過長故隱藏)
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "cstr.h"
#define INTERNING_POOL_SIZE 1024
#define HASH_START_SIZE 16
struct __cstr_node {
char buffer[CSTR_INTERNING_SIZE];
struct __cstr_data str;
struct __cstr_node *next;
};
struct __cstr_pool {
struct __cstr_node node[INTERNING_POOL_SIZE];
};
struct __cstr_interning {
int lock;
int index;
unsigned size;
unsigned total;
struct __cstr_node **hash;
struct __cstr_pool *pool;
};
static struct __cstr_interning __cstr_ctx;
#define CSTR_LOCK() \
({ \
while (__sync_lock_test_and_set(&(__cstr_ctx.lock), 1)) { \
} \
})
#define CSTR_UNLOCK() ({ __sync_lock_release(&(__cstr_ctx.lock)); })
static void *xalloc(size_t n)
{
void *m = malloc(n);
if (!m)
exit(-1);
return m;
}
static inline void insert_node(struct __cstr_node **hash,
int sz,
struct __cstr_node *node)
{
uint32_t h = node->str.hash_size;
int index = h & (sz - 1);
node->next = hash[index];
hash[index] = node;
}
static void expand(struct __cstr_interning *si)
{
unsigned new_size = si->size * 2;
if (new_size < HASH_START_SIZE)
new_size = HASH_START_SIZE;
struct __cstr_node **new_hash =
xalloc(sizeof(struct __cstr_node *) * new_size);
memset(new_hash, 0, sizeof(struct __cstr_node *) * new_size);
for (unsigned i = 0; i < si->size; ++i) {
struct __cstr_node *node = si->hash[i];
while (node) {
struct __cstr_node *tmp = node->next;
insert_node(new_hash, new_size, node);
node = tmp;
}
}
free(si->hash);
si->hash = new_hash;
si->size = new_size;
}
static cstring interning(struct __cstr_interning *si,
const char *cstr,
size_t sz,
uint32_t hash)
{
if (!si->hash)
return NULL;
int index = (int) (hash & (si->size - 1));
struct __cstr_node *n = si->hash[index];
while (n) {
if (n->str.hash_size == hash) {
if (!strcmp(n->str.cstr, cstr))
return &n->str;
}
n = n->next;
}
if (si->total * 5 >= si->size * 4)
return NULL;
if (!si->pool) {
si->pool = xalloc(sizeof(struct __cstr_pool));
si->index = 0;
}
n = &si->pool->node[si->index++];
memcpy(n->buffer, cstr, sz);
n->buffer[sz] = 0;
cstring cs = &n->str;
cs->cstr = n->buffer;
cs->hash_size = hash;
cs->type = CSTR_INTERNING;
cs->ref = 0;
n->next = si->hash[index];
si->hash[index] = n;
return cs;
}
static cstring cstr_interning(const char *cstr, size_t sz, uint32_t hash)
{
cstring ret;
CSTR_LOCK();
ret = interning(&__cstr_ctx, cstr, sz, hash);
if (!ret) {
expand(&__cstr_ctx);
ret = interning(&__cstr_ctx, cstr, sz, hash);
}
++__cstr_ctx.total;
CSTR_UNLOCK();
return ret;
}
static inline uint32_t hash_blob(const char *buffer, size_t len)
{
const uint8_t *ptr = (const uint8_t *) buffer;
size_t h = len;
size_t step = (len >> 5) + 1;
for (size_t i = len; i >= step; i -= step)
h = h ^ ((h << 5) + (h >> 2) + ptr[i - 1]);
return h == 0 ? 1 : h;
}
cstring cstr_clone(const char *cstr, size_t sz)
{
if (sz < CSTR_INTERNING_SIZE)
return cstr_interning(cstr, sz, hash_blob(cstr, sz));
cstring p = xalloc(sizeof(struct __cstr_data) + sz + 1);
if (!p)
return NULL;
void *ptr = (void *) (p + 1);
p->cstr = ptr;
p->type = 0;
p->ref = 1;
memcpy(ptr, cstr, sz);
((char *) ptr)[sz] = 0;
p->hash_size = 0;
return p;
}
cstring cstr_grab(cstring s)
{
if (s->type & (CSTR_PERMANENT | CSTR_INTERNING))
return s;
if (s->type == CSTR_ONSTACK)
return cstr_clone(s->cstr, s->hash_size);
if (s->ref == 0)
s->type = CSTR_PERMANENT;
else
__sync_add_and_fetch(&s->ref, 1);
return s;
}
void cstr_release(cstring s)
{
if (s->type || !s->ref)
return;
if (__sync_sub_and_fetch(&s->ref, 1) == 0)
free(s);
}
static size_t cstr_hash(cstring s)
{
if (s->type == CSTR_ONSTACK)
return hash_blob(s->cstr, s->hash_size);
if (s->hash_size == 0)
s->hash_size = hash_blob(s->cstr, strlen(s->cstr));
return s->hash_size;
}
int cstr_equal(cstring a, cstring b)
{
if (a == b)
return 1;
if ((a->type == CSTR_INTERNING) && (b->type == CSTR_INTERNING))
return 0;
if ((a->type == CSTR_ONSTACK) && (b->type == CSTR_ONSTACK)) {
if (a->hash_size != b->hash_size)
return 0;
return memcmp(a->cstr, b->cstr, a->hash_size) == 0;
}
uint32_t hasha = cstr_hash(a);
uint32_t hashb = cstr_hash(b);
if (hasha != hashb)
return 0;
return !strcmp(a->cstr, b->cstr);
}
static cstring cstr_cat2(const char *a, const char *b)
{
size_t sa = strlen(a), sb = strlen(b);
if (sa + sb < CSTR_INTERNING_SIZE) {
char tmp[CSTR_INTERNING_SIZE];
memcpy(tmp, a, sa);
memcpy(tmp + sa, b, sb);
tmp[sa + sb] = 0;
return cstr_interning(tmp, sa + sb, hash_blob(tmp, sa + sb));
}
cstring p = xalloc(sizeof(struct __cstr_data) + sa + sb + 1);
if (!p)
return NULL;
char *ptr = (char *) (p + 1);
p->cstr = ptr;
p->type = 0;
p->ref = 1;
memcpy(ptr, a, sa);
memcpy(ptr + sa, b, sb);
ptr[sa + sb] = 0;
p->hash_size = 0;
return p;
}
cstring cstr_cat(cstr_buffer sb, const char *str)
{
cstring s = sb->str;
if (s->type == CSTR_ONSTACK) {
int i = s->hash_size;
while (i < CSTR_STACK_SIZE - 1) {
s->cstr[i] = *str;
if (*str == 0)
return s;
++s->hash_size;
++str;
++i;
}
s->cstr[i] = 0;
}
cstring tmp = s;
sb->str = cstr_cat2(tmp->cstr, str);
cstr_release(tmp);
return sb->str;
}
TODO