contributed by < weihsinyeh >
1
改成版本二的原因是為了不使用 libm 的 log2,因為log 2 會導致錯誤。
Domain error: x is negative errno is set to EDOM. An invalid floating-point exception (FE_INVALID) is raised.
Pole error: x is zero errno is set to ERANGE. A divide-by-zero floating-point exception (FE_DIVBYZERO) is raised.
但這裡個錯誤如果設定好 N 不會發生。接下來是精度問題。
參考 Find the log base 2 of an integer with a lookup table
作業使用 flp2( x ) = 1 << ( 31 - nlz( x ))
,用這個方式的缺點如下
However, for these relations to hold for and the computer must have its shift instructions defined to produce 0 for shift amounts of –1, 32, and 63. Many machines (e.g.,PowerPC) have “mod 64” shifts, which do this. In the case of –1, it is adequate if the machine shifts in the opposite direction (that is, a shift left of –1 becomes a shift right of 1).
根據 POWER-OF-2 BOUNDARIES
求 unsigned x 的rounding up 的八倍數。
( x + 7 ) & –8
先+ 7
round up再 & -8
。-8
的2's : 1...11000
可以把多餘的餘數去掉。
求 signed x 的rounding up 的八倍數,改變求 unsigned x 的 t。
t ← ( x >> 31 ) & 7; ( x + t ) & –8
所以是正數 t 會變成 0 ,負數會 t 會為 7。
x = -31 2's: 11100001
(一) arithmetic right shift ( x >> 7 )
後為 11111111
。
(二) & 7 2's: 00000111
得到 t : 00000111
。
(三) x + t :11100001
+ 00000111
= 11101000
(四) & 11111000
= 11101000
= -24
x = 31 2's: 00011111
(一) arithmetic right shift ( x >> 7 )
後為 00000000
。
(二) & 7 2's: 00000111
得到 t : 00000000
。
(三) x + t :00011111
+ 00000000
= 00011111
(四) & 11111000
= 00011000
= 24
但第二步每次只要是負數都會是 t : 00000111
,是正數 t : 00000000
?
x & ((-1) << k )
(x >> k )<< k
接下來是逼近的概念。我在這裡想最久的是
一開始
那就舉個例子來看:
一開始是 1010
。
想了一天終於想通了,原先我一直很困惑題目解說是用 110
就回傳 6。但我一直無法聯想到到底一開始為何會有 6 ? 要如何逼近它?因為這很像一個程式碼輸入整數要求回傳二進位。並不是要求平方根的程式碼。
測驗題這句話 原理是將欲求平方根的 N^2拆成
。我後來看懂程式碼才知道
再來是當
是改成
依據
依據
雖然如此,還是不妨礙解釋程式碼,回到上面 36 的例子,要求他的平方根,那就是
知道目標後,先寫一個將十進位 X
轉換為二進位的方式,寫 i=32
是因為整數用四個位元組,一個位元組八個位元。然後這個程式就是把如果從最高位元
ret = 0;
for(i = 32; ;i-=1){
ret << 1
if( >= 2^i){
ret = (ret+1);
X -= 2^i
}
}
由於看到 i 都會被拿來用作 2 的冪次,所以我們可以簡化為,然而這依然有從 i = 2^32
很耗時的問題。
for(i = 2^32; ;i>>1){
ret << 1
if(X >= i){
ret = (ret+1);
X -= i
}
}
解決耗時問題,修正成 i = fls(N)
。對應測驗程式碼i = 1UL << ((31 - __builtin_clz(X))
for(i = fls(X);;i>>1){
ret << 1
if(X >= i){
ret = (ret+1);
X -= i
}
}
思考完後這還是給定 X 轉換二進位的程式碼。但要如何從這個改成平方根,首先就是要改變 if 判斷式的條件,而判斷式內要做的是為將位元設定為 1 則不用修改,因為輸出平方根也需要將位元設為 1 。因此設為 1 的時機點要調整,要將原先用於輸出二進位的改為輸出平方根。而這裡要用二進位做平方根的原因,我認為是因為想要用這個遞迴作逼近的功能。同時當如果高位元已經滿足以剛好求得平方根,則不用考慮低位元。同時如果先考慮低位元,缺點是後續高位元如果符合平方根的話,低位元要調整。
接下來就是
for(i = fls(X);;i>>1){
ret << 1
if(X >= i^2){
ret = (ret+1);
X -= i^2
}
}
然而這是個錯誤範例因為 X
跟 平方根 i^2
,絕對不是將每個位元所表示的 2 的冪取平方。因為這樣會變成錯誤
正確
此為
也因此有了下面
因為每次看到上面的迴圈每次都只經過一個位元(第x個位元),確認
因此 X
?下一輪
而這個剛好右等於
for(i = fls(X);;i>>1){
ret << 1
if(X >= P^2){
ret = (ret+1);
...
}
}
接下來的問題是如何每一輪都不要重複計算
m+1
是在m
右邊的位元,為了對應到測驗解說更方便)。然而這件事沒有解決問題因為要計算平方
然而這樣又有一個問題,記住了
接下來看前一輪
終於得到通式了:
這一輪第一項寫為
前一輪第一項寫為
第一輪先知道 d = 1UL << (31 - __builtin_clz(x))
知道從哪裡開始後。
接下來迴圈的第一輪找到找
因爲
已知
因此初始化
而d = 1UL << ((31 - __builtin_clz(x)) & ~1UL)
用& ~1UL
的用途是把最後一個位元清掉。
先舉例如下:
(一)x = 64 左移量為 6 (偶) ,最後一個位元清掉無效果, d=64
(二)x = 32 原先左移量為 5 (奇) ,最後一個位元清掉左移量為 4 (偶) ,d=16
上面別寫奇數偶數表達。因為迴圈每回合
用 (二)x = 32 作反例
也因此我們要先確保左移量為偶數。那為甚麼為了確保的方式,是讓奇數的最後一個位元也就是
這件事情我花了一個晚上寫數學式子想去證明,然而現在卻發現他就是那麼的直覺。雖然沒用,但經過一個晚上使用 Letax 的能力有提升。
上面的問題在於
先用2a+1當成這個
再來繼續把鄰居寫出來
int c = 0;
for (int d = 1UL << ((31 - __builtin_clz(x)) & ~1UL); d; d >>= 2) {
int b = c + d;
c >>= 1;
if (x >= b)
x -= b, c += d;
}
return c;
初始化完代表開始考慮
由於
那是因為在上一輪
然而因為我重新思考後修正之前的想法,原本以為當
看下一輪的
所以當
由於只要計算
使程式不依賴 GNU extension,且提供分支和無分支 (branchless) 的實作。
2
3
4
5
1
v : b3 b2 b1 b0
v>>1 : b4 b3 b2 b1
& : 0 1 1 1
n : 0 b3 b2 b1
n>>1 : b5 0 b3 b2
& : 0 0 1 1
n : 0 0 b3 b2
n>>1 : b6 0 0 b3
& : 0 0 0 1
n : 0 0 0 b3
因此下面這些數字也有相同效果。但由於運算元的位元都在前面步驟變為 0 。所以寫成 & 0x77777777 不影響結果,此外 & 的常數都為 0x77777777 的優點是較容易記憶其他 population count 的核心概念(右移)。
n = (v >> 1) & 0x77777777;
v -= n;
n = (n >> 1) & 0x33333333;
v -= n;
n = (n >> 1) & 0x11111111;
v -= n;
用範例 input(
上面的作法用 divide and conquer 先將以每 4 個位元 (nibble) 去計算 1 的數量。
第一個思路:將一串位元,二個位元一組去計算 1 的數量,接著四個位元一組去儲存相加上步驟的兩組的結果,八個位元一組…。
1's turn 1 0 1 1 1 1 0 0
2's turn 0 1|1 0|1 0|0 0
3's turn 0 0 1 1|0 0 1 0
4's turn 0 0 0 0 0 1 0 1
將第一個思路轉化成程式:
x 1 0 1 1 1 1 0 0 (1's turn)
& 0x55 0 1 0 1 0 1 0 1
= A 0 0 0 1 0 1 0 0
x >> 1 0 1 0 1 1 1 1 0 x 1 0 1 1 1 1 0 0
& 0x55 0 1 0 1 0 1 0 1 & 0xAA 1 0 1 0 1 0 1 0
= B 0 1 0 1 0 1 0 1 1 0 1 0 1 0 0 0
A + B 0 1|1 0|1 0|1 0 (2's turn) >> 1 0 1 0 1 0 1 0 0 (B)
& 0x33 0 0 1 1 0 0 1 1
...
% 0x0F 0 0 0 0 1 1 1 1
右邊使用 (x & 0xAA) >> 1 增加可讀性,書上的說明: 看不懂
《Hacker's Delight》P85
The code avoids generating two large constants in a register. This would cost an instruction if the machine lacks the and not instruction.
x = x - ((x >> 1) & 0x55555555);
x 1 0 1 1 1 1 0 0
x >> 1 0 1 0 1 1 1 1 0
& 0x55 0 1 0 1 0 1 0 1
0 1 0 1 0 1 0 0
0 1 1 0 1 0 0 0
因為上面有點難說明操作背後的意義,所以以兩個位元分成4個情況討論
x 1 1 1 0 0 1
x >> 1 0 1 0 1 0 0
01 0 1 0 1 0 1
(( x>>1 ) & 01) 0 1 0 1 0 0
x - (( x>>1 ) & 01) 1 0 0 1
兩個位元能夠出現 1 的數量可能性的結果有 2(10) 與 1(01) 與 0(00),因此當 x 是 (11) 時,則結果會是 2(10), x 是 (10)或(01) 時,則結果會是 1(01), x 是 0(00) 時,則結果會是 0(00)。
(( x>>1 ) & 01) 提取第二個位元的值 (0 或 1)。觀察 x 與結果的值,發現差第二個位元
x = x - ((x >> 1) & 0x55555555); // x = (x & 0x55555555) + ((x>>1) & 0x55555555)
x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
x = (x + (x >> 4)) & 0x0F0F0F0F; // x = (x & 0x0F0F0F0F) + ((x>>4) & 0x0F0F0F0F)
x = x + (x >> 8);
x = x + (x >> 16);
res = x & 0x0000003F;
4 5 6 行原先的操作是寫作這樣,下面用例子說明:
x = (x & 0x00FF00FF) + ((x>>8) & 0x00FF00FF);
x = (x & 0x0000FFFF) + ((x>>16) & 0x0000FFFF);
0 0 0 0 0 1 0 1|0 0 0 0 0 1 0 0|0 0 0 0 0 1 1 0|0 0 0 0 1 0 0 0
原先的操作 : 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1|0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0
修正的操作 : 0 0 0 0 0 1 0 1 0 0 0 0 1 0 0 1|0 0 0 0 0 1 1 0 0 0 0 0 1 1 1 0
原先的操作 : 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 1
修正的操作 : 0 0 0 0 0 1 0 1 0 0 0 0 1 0 0 1|0 0 0 0 0 0 0 0 0 0 0 1 0 1 1 1
& : 0 0 0 0 0 0 3 F
最後之所以是 0x3F 是因為總共只有 32 個位元。所以用6個位元就可以表示其數量
接下來這個要看第 i 個位元
只有 4 個位元時
將這個公式連接到二個位元的事情。也就是第一輪到第二輪的數學表示。
1's turn 1 0 1 1 1 1 0 0
2's turn 0 1|1 0|1 0|0 0
二個位元可以想成四進位。
因此把上面數學式子轉換成
x b3 b2 |b1 b0 -> (b3*2+b2)*4 + (b1*2+b0)*1
-) 0 b3 b2 0
-) 0 0 b3 b2
先右移二再乘 3(0x11),相當於做了(右移一後再減)兩次,因此 sum_digits(x) =
擴展成 0x3333333
用上面的概念就可以解釋測驗題的程式碼,sum_digits(x) =
x b3 b2 b1 b0 |-> (b3)*2^3+(b2)*2^2+(b1)*2+(b0)*1
-) 0 b3 b2 b1
-) 0 0 b3 b2
-) 0 0 0 b3
因此改成做了(右移一後再減)三次。原先覺得 & 0x77777777
與 & 0x33333333
與 & 0x11111111
也可以,但是讀上說明才發現都寫成 & 0x77777777
的優點如下說明:
《Hacker's Delight》 P89
The repeated use of the mask& 0x77777777
permits loading it into a register and referencing it with register-to-register instructions.
讀這本書的時候,原先有很多想法都會在後面被提到並且說明優缺點。像是他說明逐一將最右邊(right-most)的 1 位元清掉(《Hacker's Delight》P23 ),接著寫在迴圈 while(x != 0){ x = x & (x-1);}
計算總共做了幾輪,這樣的操作在 1 比較少的時候會較快,如果 1 很多就用 while(x != -1){x = x | (x+1);}
來將最右邊的 0 設為 1。這樣就因應不同的情況,而不用每次都先平行計算次數再用 divide and conquer 相加結果。
因為 divide and conquer 是將要處理的單元先分成 niddle 為單位在兩兩一組相加原先在單位中計算的整數,因此這已經是平行運算,所以要血更高效的,我首先不會改這裡。我改的地方是:
v = (v + (v >> 4)) & 0x0F0F0F0F;
v *= 0x01010101;
v = v>>24
return v
因為乘的數字太大了。我把第 2、3 行改成下面這一行。
v -= 255*((v>>8)+(v>>16)+(v>>24));
原理來自公式 :
v b7 b6 b5 b4 b3 b2 b1 b0
v>>1 0 b7 b6 b5 b4 b3 b2 b1
&0x0F0F0F0F_________________________
0 b6+b7 0 b4+b5 0 b2+b3 0 b0+b1
由上面得知是以
但這件事情讓我很好奇,為什麼不一開不直接用 2 進位。這樣公式會變成
也就是測驗題的公式這樣程式碼會變成。
unsigned n = v;
while(n){
n >>= 1;
v -= n;
}
但這樣效率會變低,代表平行是加快程式效能的關鍵。那接下來做的事情是將程式盡量改成平行的版本。
2
3
xorshift 亂數產生器,
如何測試
ldexpl
fusion operation
x * ( 2^exp )
x & 0x7fff sign 算有號
x = (x >> CCCC)
靜態區域變數 static 其實就是 全域變數。
population count
ECC
BCH
crt
trusted computing base
sudo insmod hello.ko
變成 linux kernel 不可分割的一部分。EEVDF
EAS
Mach microkernel
LTE : long-term evolution (2G -> 3G CDMA 3.999999)
現代作業系統 long-term 。
dynamic frecuency scaling
L4 microkernel
何時會發生 context switch.
process 在 user mode 交換 kernel mode 可能不會 depend implementation
context switch
x86-64 指令集是 AMD 發明。
IA32 是 intel
IA64 是 intel
x86-64 AMD
x86s 去掉 32
cpumask_t : migration_disabled
多數處理器都是多核。
cpu hotplug: 滑動頁面,最耗 cpu 資源。播放影片 decoder 可以用最少的 cpu 資源。
NUMA : 傳輸需要時間。 cpu migrate 到其他 cpu 。
ls -lh /sys/devices/system/cpu
孟母是自願 migrate
cpu 頻率會變動, cpu 會 idle。
RCU 同步 lock free 與 semphore 與 mutex 不一樣。
mm 記憶體管理,稀疏問題,要考慮並行,同時效率要高,因為存取頻率太高。
schedule 裡面有一堆 lock 。
ttwu
rt_mutex
C_groupts
brk 系統呼叫 為了相容 Kconfig
可以找到描述用的規範。用git log 只能看2005年之後的東西
mallco calloc 提供給 user space 的競爭。
snmalloc 讓兩個以上的行程去做通訊。
CTSS
jitter 最大延遲 - 最小延遲
work queue
不能被 dereference
#define UNALIGNED(X) ((long) X & (sizeof(long) - 1))
const void * str = (const void char *) str
unsigned char d = c;
轉型要讓它算術,參數可以接受任意存取,但之後需要轉型。使用者看自己的應用場景去強制轉型,才能去存取最後的物件。
64 bits。
BBBB = mask << i;
long 的寬度跟 word 的寬度是一樣的。
XOR 跟比較字元相通相消。
只要一個byte 是 0 就成立,detect_char 就會成立。
probabiltiy 擴充。
很多架構不能隨意存取地址。沒有對齊過的,不能存取。
RUNNING ready 與 running。