contributed by <bonianlee
>
1
參照 你所不知道的 C 語言: bitwise 操作,考慮 next_pow2 可針對給定無號 64 位元數值 x,找出最接近且大於等於 2 的冪的值,例如:
next_pow2(7)
= 8next_pow2(13)
= 16next_pow2(42)
= 64測驗題題目中提到的一種實作方式為:
它採用的方法是先取 uint64_t
的最小與最大的 2 的冪的值,分別以次冪數形式儲存在 lo
跟 hi
中,也就是 與 ,並且將 x
與 test = (lo + hi)/2
次冪數的值 做比較,若 x
比較小,則將上限次冪 hi
縮小成 test
的值,反之,若 x
比較大,則將下限次冪 lo
增加到 test + 1
的值,最後的回傳結果有兩種
return pow2(test)
x
剛好等於 時,則直接回傳 return pow2(lo)
x
並不是 2 的冪的值,則回傳最接近且大於 x
的 2 的冪的值,即 而題目提到另一種使用位元運算的實作分支,它所做的事情是以擁有最大位元的 1
為界,向後「填補」位元為 0
的位元
當「填補」的動作完成後,為了符合預期,找出最接近且大於等於 2 的冪的值,可以進行以下操作
則可以得到預期的結果
欲在 64 位元的大小完成以上實作,嘗試改寫為以下:
舉例來說, 64 位元的 x = 00100...0
丟進 next_pow2
執行結束後,會得到 00111...1 + 1 = 01000...0
,換成十進位為 ,即為預期的輸出
延伸問題
__builtin_clzl
改寫
int __builtin_clz (unsigned int x)
Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined.
int __builtin_clzl (unsigned long)
Similar to__builtin_clz
, except the argument type is unsigned long.
clz
內建函式已運用時,編譯器能否產生對應的 x86 指令?提示: 可執行
cc -O2 -std=c99 -S next_pow2.c
並觀察產生的next_pow2.s
檔案,確認bsrq
指令 (表示 “Bit Scan Reverse”)
__builtin_clzl
改寫我的作法是使用 __builtin_clzl
取得 x
的領頭 0-bits 數量,間接得知最接近最高位元的 1-bit 所在的是第幾個位元,於是我使用右移位移運算子將左邊出現的第一個 1-bit 移至最低位元的右邊,也就是說我將所有 1-bits 清除,如下例
接著將 x
代 1 ,再將它左移至原本最左邊出現的 1-bit 的左邊一個位元
至此步驟已經實現了找出大於 x
的最小 2 的次冪,不過根據題意,當 x
剛好是 2 的次冪時,應回傳原本的數,當我正要開始思考判斷式該如何寫時,< fewletter >提出了一個很棒的想法,我們看下面這行程式碼
input
是要丟入 next_pow2()
執行的 64 位元數值,若將 input - 1
丟入 next_pow2()
中執行,就可以達成前述的效果
首先觀察 if
判斷式的 ==
左邊, fmt->fmt.pix.pixelformat
代表像素格式的設定,因此這裡是在提取目前的格式設定。而 ==
右邊則是 XRGB 的格式,'x', 'R', 'G', 'B'
個別代表透明度、紅、綠、藍色的像素格式識別碼,它們各佔有 8 bits ,總共有 32 bits 。若 if
判斷式滿足即表示目前的格式設定為 XRGB 格式,代表系統支援 XRGB ,則可將像素格式設定為 V4L2_PIX_FMT_XRGB32
clz
內建函式已運用時,編譯器能否產生對應的 x86 指令?提示: 可執行
cc -O2 -std=c99 -S next_pow2.c
並觀察產生的next_pow2.s
檔案,確認bsrq
指令
執行後產生的.s
檔部份程式碼
結果顯示有產生對應的 bsrq
指令
2
if
判別式在 for
迴圈中的作用是判斷 i
是否為 2 的次冪,判別方式如下
判斷 2 的次冪是為了要進行位元左移, i
的值不斷增加,當剛好變為 2 的次冪時,二進位表示法即會進位一次,此時位元左移的次數就要加 1
由 |
將 i
與左移的 ans
接在一起
詢問 ChatGPT 上述程式碼中 M
的用途
得知 M
的用途是為了限制結果不要超出範圍以外,稱之為「對 M 取模」
延伸問題
__builtin_{clz,ctz,ffs}
改寫,並改進 mod 的運算__builtin_clz()
與 __builtin_ctz()
重新實作使用 __builtin_clz()
跟 __builtin_ctz()
這兩個函式,我就可以找出從頭端跟從尾端數到 1-bit 為止的 0-bits 的數量,假如 i
是 2 的次冪的話,前述兩個函式的總和值 +1 就會等於 int
資料型態的空間大小 4 bytes ( 32 bits )
concatenatedBinary()
回傳之資料型態為 int
,但 ans
的資料型態為 long int
,經過 M
取模後可能會有 overflow 的問題,原因是 M
所取的質數為 ,連 int
最大值 2,147,483,647 的一半都還不到,若想完整地串連起來擁有較大值的 int
型態級數就會出現問題M
取值為比 還要小的最大質數 ( k 為 ans
資料型態所佔用的 bits 數) ,故可取 M
,不過函式的傳回值必須改為 long
使用修改後的程式碼重新測試 n = 12
時的結果為 共 12 位數,近似於 ,比原本取模 M
時的結果還要大,且也符合真正的位元數
雖然結果符合預期,但光是 n = 12
就需要佔用 37 bits 的空間,約為 M
所佔用空間 64 bits 的一半,因此能操作的 n
值上限不高,為了避免 overflow 的潛在不安因素,可以加入判斷式限制 n
的值不要超過上限即可
假如 n = 25
為安全範圍的上限,進行例外排除
3
參考 < seasonwang > ,修正編譯有誤的片段程式,如下所示
qword
指向 buf
的地址,而 end
則指向 qword[len >> 3]
的地址, len >> 3
相當於 len / 8
for
迴圈的作用是計算 qword
到 end
之間不為 UTF-8 的 bytes 數,配合 __builtin_popcountll
計算個數(1 << 3) * (len / 8)
會得到 8 的倍數,再扣除 count
後可得 UTF-8 的總數len & 7
可視為 len % 8
的反義,若 len
為 8 的倍數,則 len & 7
為 false
,故 count += 0
len
不為 8 的被數,則進入函式 count_utf8
以計算剩餘的 bytes 是否還有 UTF-8 ,最後回傳的 count
即為 buf
中之 UTF-8 總數4
以上的程式碼是在判斷 x
的 Most Significant Bit ( MSB ) 是否為 1 ,並且它後面的位元為連續的 1-bit ,例如
若滿足這種型態,則回傳 true
,反之則回傳 false
若要將程式碼進行改寫,達成等價的行為,但更精簡有效,可以寫成如下形式
原理是藉由 ~x + 1
使 n
中最接近 Least Significant Bit ( LSB ) 的 1-bit 所在的位元與 x
相同,如此一來便可藉由 n XOR x
將最靠近 LSB 的 1-bit 變為 0-bit ,而其他位元則如同執行 OR
運算,如下所示
上述的運算中,可以看到一個性質,就是 MSB 之後是否為連續的 1-bits 將會影響運算結果的大小,舉例來說
藉由此性質,可以判斷 x
是否符合特定樣式
cpumask_t
,它在 Linux Kernel 中被廣泛運用在檢視及操作 CPU 的狀態,位於 include/linux/bitmap.h
當中cpumask_of()
是 Linux kernel 中一個用來創建 CPU 集合的函數,其原型定義如下:
該函數接受一個整數 cpu,返回一個包含指定 CPU 的位元遮罩 cpumask_t
,例如, cpumask_of(0)
會返回一個只有第一個 CPU 為 1,其他位元都為 0 的 cpumask_t
值。
cpumask_of()
函數常被用於創建進程的 CPU 選擇集合。在 Linux kernel 中,每個進程都可以綁定到一個或多個 CPU 上運行。進程的 CPU 選擇集合通常表示為一個 cpumask_t
位元遮罩,用來指定哪些 CPU 可以用於運行進程。
例如,以下代碼展示了如何使用 cpumask_of()
函數創建一個包含第一個 CPU 和第二個 CPU 的選擇集合:
在這個例子中, cpumask_clear()
函數用來初始化 my_cpuset
為空集合。接下來, cpumask_set_cpu()
函數用來將第一個 CPU 和第二個 CPU 分別加入選擇集合中。
透過使用 cpumask_of()
函數,可以方便地創建包含指定 CPU 的選擇集合,進而用於進程的 CPU 調度和管理。
ChatGPT
它的功能是取得指定 CPU 的遮罩,得以檢視及更動該 CPU 的狀態
假如一項進程需要 5 個 CPU 運行,則可以透過 cpumask_set_cpu
來選擇要綁定的 5 個 CPU 運行進程