Try   HackMD

2024q1 Homework4 (quiz3+4)

contributed by < w96086123 >

第三週測驗

測驗一

公式解釋

為求

N2 的開根號,可利用 Digit-by-digit calculation 的方式取得。

可將

N2 拆分為由 n 個整數之和:
N2=(an+an1+an2+...+a0)2,am=2m or am=0

將此展開之後可得:
N2= an2+[2an+an1]an1+[2(an+an1)+an2]an2+...+[2(i=1nai)+a0]a0= an2+[2Pn+an1]an1+[2Pn1+an2]an2+...+[2P1+a0]a0Pm= an+an1+...+am= pm+1+am

因此可知
P0
即為所求。

目的是從試著找出所有

am ,因此由 n 試到 0 並且因為
am
只有兩種可能
am=2mor 0
,因此求
am
時只須試驗
Pm2N2
,即可知道
am
的值。
{Pm=Pm+1+2m,if Pm2N2Pm=Pm+1,otherwise

由於每輪計算

N2Pm2 的成本過高,因此改為利用上輪的差值
Xm+1
減去
Ym
可得
Xm

Xm=N2Pm2=Xm+1Ym

Ym=Pm2Pm+12=2Pm+1am+am2

也就是紀錄上一輪的
Pm+1
來調整。
Ym={cm+dmif am=2m0if am=0cm=Pm+12m+1dm=(2m)2

拆分
Ym
可得上述的
cm
dm
,並且藉由位元運算推出下一輪
cm1= Pm2m=(Pm+1+am)2m=Pm+12m+am2m={cm/2+dmif am=2mcm/2if am=0dm1= (2m1)2=(2m2)2=(2m)24=dm4

由上述可知 AAAA 為 2 ,BBBB 為 1 。

利用第二週測驗的 ffs 替代 __builtin_clz

int i_sqrt(int x)
{
    if (x <= 1) /* Assume x is always positive */
        return x;

    int z = 0;
    for (int m = 1UL << ((31 - ffs(x)) & ~1UL); m; m >>= 1) {
        int b = z + m;
        z >>= 2;
        if (x >= b)
            x -= b, z += m;               
    }
    return z;
}

在 Linux 核心原始程式碼找出對整數進行平方根運算的程式碼,並解說其應用案例

此函數在 \lib\math\int_sqrt.c

unsigned long int_sqrt(unsigned long x)
{
	unsigned long b, m, y = 0;

	if (x <= 1)
		return x;

	m = 1UL << (__fls(x) & ~1UL);
	while (m != 0) {
		b = y + m;
		y >>= 1;

		if (x >= b) {
			x -= b;
			y += m;
		}
		m >>= 2;
	}

	return y;
}

找到 int_sqrt 被用在 rwb_arm_timer 中,而 rwb_arm_timer 的路徑是 \block\blk-wbt.c

static void rwb_arm_timer(struct rq_wb *rwb)
{
	struct rq_depth *rqd = &rwb->rq_depth;

	if (rqd->scale_step > 0) {
		/*
		 * We should speed this up, using some variant of a fast
		 * integer inverse square root calculation. Since we only do
		 * this for every window expiration, it's not a huge deal,
		 * though.
		 */
		rwb->cur_win_nsec = div_u64(rwb->win_nsec << 4,
					int_sqrt((rqd->scale_step + 1) << 8));
	} else {
		/*
		 * For step < 0, we don't want to increase/decrease the
		 * window size.
		 */
		rwb->cur_win_nsec = rwb->win_nsec;
	}

	blk_stat_activate_nsecs(rwb->cb, rwb->cur_win_nsec);
}

此函數 rwb 代表的是 request write back ,因此可以知道它是一個寫入的函數。
此函數主要想要判斷是否有需要提昇寫入速度,若有需要則使用調整 window size 的方法進行提昇,且調整方法為透過 Fast inverse square root
但為何要使用 8 與 4 這樣狀況無法得知,只能猜測可能是基於實驗過後的結果所得出的結論。

測驗二

#include <stdint.h>
void divmod_10(uint32_t in, uint32_t *div, uint32_t *mod)
{
    uint32_t x = (in | 1) - (in >> 2); /* div = in/10 ==> div = 0.75*in/8 */
    uint32_t q = (x >> 4) + x;
    x = q;
    q = (q >> 8) + x;
    q = (q >> 8) + x;
    q = (q >> 8) + x;
    q = (q >> 8) + x;

    *div = (q >> CCCC);
    *mod = in - ((q & ~0x7) + (*div << DDDD));   
}

主要想法為想要使用 bitwise 的方式進行除法運算,但 10 不是 2 的冪次,因此無法直接使用,所以此方法想把 in 改變成

810in ,後使用右移三位取得
110in

但 in 無法使用直接轉成
810
,因此使用逼近法取得數值 q = (q >> 8) + x
根據上述的計算過程,可得知要取得商數只需要將 q 右移三位即可,因此 CCCC 為 3 。
則想要取得餘數可利用餘數定理取得,((q & ~0x7) + (*div << DDDD)) 則為
gQ
,因此
mod=ingQ
即為餘數。

測驗三

主要作法與 __builtin_clz 相似,尋找 MSB 的數值以取得以 2 為底的對數整數。
以長度為 32 位元的為例,若 i 大於

216 代表前 16 位元中含有 bit 為 1 ,以此類推可得以下程式:

static size_t ilog2(size_t i)
{
    size_t result = 0;
    while (i >= 65536) {
        result += 16;
        i >>= 16;
    }
    while (i >= 256) {
        result += 8;
        i >>= 8;
    }
    while (i >= 16) {
        result += 4;
        i >>= 4;
    }
    while (i >= 2) {
        result += 1;
        i >>= 1;
    }
    return result;
}

由於此方法可以直接使用 __builtin_clz 代替,因此也可得以下程式:

int ilog32(uint32_t v)
{
    return (31 - __builtin_clz(v));
}

測驗四

測驗五

int ceil_ilog2(uint32_t x)
{
    uint32_t r, shift;

    x--;
    r = (x > 0xFFFF) << 4;                                                                                                                                    
    x >>= r;
    shift = (x > 0xFF) << 3;
    x >>= shift;
    r |= shift;
    shift = (x > 0xF) << 2;
    x >>= shift;
    r |= shift;
    shift = (x > 0x3) << 1;
    x >>= shift;
    return (r | shift | x > 1) + 1;       
}

shift 用來記錄當下 msb 的值,並且使用 r 進行 shift 的累加,以取得 ilog2 的數值。在這過程中,需要注意到我們要取得的是上限值,因此在一開始進行 -1 ,主要為了處理 x 剛好為 2 的指數次方狀況。如果沒有做此動作,在輸出時會因為回傳的 +1 而比正確答案多一。

改進程式碼,使其得以處理 x = 0 的狀況,並仍是 branchless

可以在進行運算前,先進行判斷是否 x 為 0 ,若是則改為 1 。後續處理方式一樣,程式碼如下。

int ceil_ilog2(uint32_t x)
{
    uint32_t r, shift;

    x |= (x == 0);

    x--;
    r = (x > 0xFFFF) << 4;
    x >>= r;
    shift = (x > 0xFF) << 3;
    x >>= shift;
    r |= shift;
    shift = (x > 0xF) << 2;
    x >>= shift;
    r |= shift;
    shift = (x > 0x3) << 1;
    x >>= shift;
    return (r | shift | x > 1) + 1;
}