contributed by < robertnthu
>
linux2022
EXP1 = a & b & 1
EXP2 = a & b
EXP3 = a ^ b
EXP1
a >> 1
就是 a / 2
,b >> 1
就是 b / 2
(a >> 1) + (b >> 1)
都會是(a + b) / 2
(a >> 1) + (b >> 1)
後,其餘情況都是0a & b & 1
來實現
a & b & 1
= 1EXP2, EXP3
參考:你所不知道的 C 語言:數值系統篇
程式
組合語言
利用 gcc -O2 -S test.c
來編譯程式,-O2
是開啟最佳化,取出程式片段
%esi
存的是 a 的值,把 a 的值放到 %eax
, 而 b 放在 %edi
subl %edi %eax
是 %eax = %eax - %edi
,也就是 a - b 的運算shrl %eax
shift right,是 (a - b) / 2 的運算addl %edi, %eax
再加上 b ,就完成了以 instruction 數量來說,b + (a - b) / 2
是最少的
但是它有一個addl
以及subl
指令
而(a & b) + ((a ^ b) >> 1)
雖然多一個 instruction,但是只有一個addl
指令。
BUILD_BUG_ON
: 參考linux tricks 之 BUILD_BUG_ON_ZERO.這個巨集是為了檢查 condition
這個數,如果 condition == 0
會回傳 1,若condition != 0
則會回傳 -1
WRITE_ONCE
參考Linux内核的WRITE_ONCE函数分析,volatile
可以確保這個指令不會因為編譯器最佳化而省略,為了解決編譯時同步問題的方法
average.h
The third argument, the weight reciprocal, determines how the new values will be weighed vs. the old state, new values will get weight 1/weight_rcp and old values 1-1/weight_rcp. Note that this parameter must be a power of two for efficiency.
所以 new values 要乘上 1/weight_rcp,old values 要乘上 1-1/weight_rcp,兩個相加就是 EWMA。
這個程式是最主要的計算,internal 就是 old values
為了計算方便,令 weight_rcp 是 2 的冪,這樣乘法運算只需要用 shift right 跟 shift left 就可以完成
EXP4 = a ^ b
EXP5 = a <= b
回顧解讀計算機編碼
(diff >> 31)
diff & (diff >> 31)
就是 0diff & (diff >> 31)
就還是 diff
題目要求是以下程式
接下來根據提示來回答,希望的操作是
max(a, b) = (a > b) ? a : b
EXP4 & -(EXP5)
等於 0,此時 EXP5 = 0
a ^ 0
= a,所以是 a > b 的結果EXP4 & -(EXP5)
等於 EXP4,此時 EXP5 = 1
a ^ EXP4
a ^ EXP4
等於 b我認為在不考慮「uint32_t 會出現負數」的前提下,兩者的實作是一樣的。Assign 一個負數給 unsigned int,在 "<=" 的判斷會出錯。負數因為 sign bit 的關係,會判斷為「負數 > 正數」
如何利用git log
檢索
COND = v & (-1) 或 v | 0
RET = u << shift
我認為這是因為, mod 運算本身可以用減法實現。
如 37 mod 6,如果用%
可以直接計算出餘數,但是如果用大減小的方式,37 - 6, 31 - 6, 25 - 6, …,最後一樣可以得到同樣的結果
if (!u || !v) return u | v;
檢查 u, v 是否都非零為什麼要把 u, v 各自 shift 到 1-bit 不為 0?
v == 0
,用 bitwise 操作就是 v & (-1)
或是 v | 0
。而中止迴圈後要回傳 u,再乘上一開始 shift 的 bits 數int __builtin_ctz(unsigned int)
__builtin_ctz
來取代計算 trailing zero 的迴圈且這裡 u, v 的 right shift 可以移到後面再做,所以改寫為
使用先前「不需要分支的 min」
後續的
以及
也改寫成
以及
簡化條件判斷
由於條件判斷的部份看起來可以更簡潔,配合先前的 min(), max() 改寫
最終程式
未完成
這裡有兩種 gcd 的程式,如果__ffs
無法使用,則用第二種 gcd()
unsigned long gcd(unsigned long a, unsigned long b)
{
unsigned long r = a | b;
if (!a || !b)
return r;
b >>= __ffs(b);
if (b == 1)
return r & -r;
for (;;) {
a >>= __ffs(a);
if (a == 1)
return r & -r;
if (a == b)
return a << __ffs(r);
if (a < b)
swap(a, b);
a -= b;
}
}
如果 a 或 b 有 0 ,則回傳 r ,因為 r = a | b,所以不管 a, b 中有一個 0 還是兩者都是 0 ,都能回傳正確的值
b >>= __ffs(b);
跟先前使用 __builtin_ctz
的u >>= __builtin_ctz(u)
作用一樣,都是針對 trailing zero 做 shift rightb == 1
,則代表 b
為 2 的冪,b
的公因數只有 2 ,所以只需要確認 a 是否也有因數為 2 ,就能回傳,2 以外因數可以不用在乎,因為必定不是公因數r & -r
= (a | b) & (~(a | b) + 1)
r = a | b
,所以 a, b 的共同 trailing zero 也會是 r 的 trailing zero-r
首先會取 compliment,所以本來的 trailing zero 會變成 1 ,再加 1 之後會全部進位變成 0,且除了進位的那個 carry bit 以外的 bit,在 r & -r
運算之後都會變成 0 ,也就得到想要的最大公因數這個操作等價於
但這裡選擇的是r & -r
的做法
a >>= __ffs(a);
與先前的v >>= __builtin_ctz(v);
作用一樣
a == b
,此時就是最大公因數,因為 a, b 兩數的trailing zero 已經被 shift right 消去了,所以回傳時要再 shift left 回來,這裡等價於先前的 return u << shift
我認為這個程式與一開始使用 int shift
紀錄共同 trailing zero 以及__builtin_ctz
的程式是一樣的,只是實作方式有些微的不同
a == b
, a 就是 a, b 的最大公因數,則回傳a < b
,則 a, b兩者交換,把比較大的數放在 a,較小的數放在 ba -= b
,然後 a 向右 shift 一位
a & r
且相等的話再a += b
?
EXP6 = bitset & -bitset
其中第 9 行的作用是找出目前最低位元的 1,並紀錄到 t 變數。舉例來說,若目前 bitset 為 000101,那 t 就會變成 000001,接著就可以透過 XOR 把該位的 1 清掉,其他保留 (此為 XOR 的特性)。
根據測驗 3 lib/math/gcd.c 的程式,我們知道 r & -r
可以有留下 first set bit 並把其他 bits 都設為 0的作用,所以這題答案就是bitset & -bitset
Linux 核心設計: 不只挑選任務的排程器談到Linux 2.6 O(1) 排程器時,使用 140-bits bitmap 來作為 priority array,並且使用 find first set 來找出目前 priority 最高的 process。
在這樣的結構裡,這個程式可以找到所有需要被 schedule 的所有優先權級別
PPP = pos - -
MMM = list_add
EEE = &heads[remainder % size]
這個資料結構是為了使用 hash table 而設計的。
配合 find()
,可以在 hash table 裡面找到相對應的 key ,如果有找到同樣的 key 的話,會回傳 index。
fractionToDecimal()
程式的前半段是檢查如果 input 有 0 的情況,並可以直接回傳。
處理正負號,把 numerator, denominator 的負號都去掉,並把正確的正負號加在 *p
,也就是result
裡面,並且用 n, d 取代本來的 numerator, denominator
sprintf()
,C 語言規格書的描述
The sprintf function is equivalent to fprintf, except that the output is written into an array (specified by the argument s) rather than to a stream. A null character is written at the end of the characters written; it is not counted as part of the returned value. If copying takes place between objects that overlap, the behavior is undefined.
int pos = find(heads, size, remainder);
rem_node
,並把 remainder 當作 key、 i 當作 index 存入
MMM(&node->link, EEE);
是把 rem_node
存入 hash table 的巨集,所以 MMM = list_add() 或 list_add_tail() 都可以find()
可以知道,hash 值就是 remainder % size,所以 EEE = &heads[remainder % size]數字 + '0'
的用法是把0~9的整數換成 char,然後更新 remainder,就是國高中學的除法M = _h
X = 0
根據提示,alignof() 要回傳 t 的長度,也就是 t 佔了幾個 byte
(char *)X
,所以 (char *)X)
應該要是這個結構的起點,而(&((struct { char c; t _h; } *)0)->M)
要是這個結構加上 t 的位址,這樣相減,才會得到 t 的長度KK1 = div3
KK2 = div5
KK3 = div3 << 2
Faster remainders when the divisor is a constant: beating compilers and libdivide
You have that n/d = n * (2N/d) / (2N). The division by a power of two (/ (2N)) can be implemented as a right shift if we are working with unsigned integers, which compiles to single instruction: that is possible because the underlying hardware uses a base 2. Thus if 2N/d has been precomputed, you can compute the division n/d as a multiplication and a shift.
How do compilers compute the remainder? They first compute the quotient n/d and then they multiply it by the divisor, and subtract all of that from the original value (using the identity n % d = n - (n/d) * d).
The intuition is as follows. To divide by four, you might choose to multiply by 0.25 instead. Take 5 * 0.25, you get 1.25. The integer part (1) gives you the quotient, and the decimal part (0.25) is indicative of the remainder: multiply 0.25 by 4 and you get 1, which is the remainder. Not only is this more direct and potential useful in itself, it also gives us a way to check quickly whether the remainder is zero. That is, it gives us a way to check that we have an integer that is divisible by another: do x * 0.25, the decimal part is less than 0.25 if and only if x is a multiple of 4.
要檢查一個數會不會被 4 整除,就把那個數乘上 0.25,然後檢查小數部份,如果小於 0.25,那這個數可以被 4 整除
is_divisible
會回傳一個 bool ,如果可以整除的話,則會回傳 1,否則回傳 0觀察 unsigned int length = (2 << KK1) << KK2;
根據提示,KK1, KK2 都是變數名稱,那就是 div3, div5 了,因為 2 shift left 就是乘以 2 ,再往下看:
div5 = 1時,9 已經 shift right 變成 4 (0100)
div5 = 0時,9 還是 9 (1001)
KK3 = div3 << 2,因為 1 << 2 = 4,可滿足前述操作