執行人: 56han
專題解說影片
ssheep773
當我們計算 (p << 64) / b 時,目的是得到一個非常大的數字,使得在後續的除法操作中結果更準確。直接計算 (p << 64) / b 可能會因為整數除法而產生誤差。
這部分中非常大的數字實際大小是多少 ?並會產生多大的誤差 ?
LindaTing0106
在 TODO: 第 7 週測驗第二題中提到 __builtin_expect 的使用是為了進行更佳的分支預測及優化,那麼可以展示一下有使用與未使用在效能上面的差異嗎?
Lccgth
SWAR 技術則是通過在寄存器內部同時處理多個較小的資料單元來提高計算效率。
注意用語:
寄存器 -> 暫存器
已修正表達方式
yuyuan0625
下文解釋乘法運算比除法運算在 CPU 上執行更快的引用可以補上來源網址
yy214123
這部分的程式會將兩個 32 位元合併為 64 位元,我的想法是:
我的想法是如此已經完成合併,那為何還需要:
又或者這個步驟有其特殊目的但我沒發覺?
david965154
比較 Linux 核心原本的實作和 memchr_opt,設計實驗來觀察隨著字串長度和特定 pattern 變化的效能影響
這裡測試 Linux 核心原本的 memchr
是軟體實作版本還是硬體實作版本 ? 我自己實測下來與軟體實作版本相比, opt 結果是好上不少的,但是與硬體實作版本相比也是會表現較差。
重作第 7 週測驗題和延伸問題,探究 bitops 在 Linux 核心的應用。
包含延伸問題
Question: 為什麼 x 和 y 都要加 0L,再進行位移?
Answer: 因為 x 和 y 的位元寬是 32 位元 (uint32_t
) ,左移 32 位 個位元會超出它的位元寬,會出現 warning: left shift count >= width of type。long 是 64 位元,左移 32 個位元在這種情況下是合法的,不會引發警告。
(-1L >> 32)
計算出的值是1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111
。
延伸閱讀: SIMD and SWAR Techniques
SIMD 單指令流多資料流(Single Instruction Multiple Data,SIMD):是一種採用一個控制器來控制多個處理器,同時對一組資料中的每一個分別執行相同的操作,從而實現空間上平行的技術。
什麼「並列性」?
使用精準且明確的術語。
「並列性」一詞是參考維基百科網站,但經過閱讀並行程式設計:概念,我認為「平行」一詞更加合適。
Concurrency 指程式架構,將程式拆開成多個可獨立運作的工作,像是驅動程式都可獨立運作,但不需要平行化。Parallelism 則指程式執行,同時執行多個程式。Concurrency 可能會用到 Parallelism,但不一定要用 Parallelism 才能達成 Concurrency。
SWAR (SIMD within a register):在一個暫存器上進行單指令多資料流操作。它是一種跨 CPU 暫存器各部分應用 SIMD 平行處理的處理模型,在 SIMD(單指令多資料)並行處理模型中,這些 byte-entities(即小於一個字串的資料單位)經常被用來在一個 CPU 寄存器 暫存器的不同部分上進行並行計算。
這樣的加法操作會直接對整個 64 位 的數字 數值 進行運算,包括進位。
SWAR 技術則是通過在暫存器內部同時處理多個較小的資料單元來提高計算效率。例如,對於 64 位暫存器,我們可以將其視為包含了 8 個 8 位的 byte,並對每個 byte 進行獨立的加法運算。使用 SWAR 技術,我們可以在一個指令中並行地對所有 byte 進行加法運算。具體的 SWAR 技術 技巧加法公式如下:
其中,x &~H
和 y &~H
去掉了每個 byte 的最高位,以避免在進行無進位加法時受到影響,再透過 (x ^ y) & H
找出每個 byte 中的進位情況。
如此一來,在一次操作中就能對所有的 byte 進行加法計算,而不需要逐個處理進位,這大大提高了運算效率。
你宣稱的「大大提高了運算效率」是多少呢?能量化嗎?你檢驗過了嗎?若沒有,不就淪為人云亦云嗎?
via 和 through 不該翻譯為「通過」,否則無法區分 "pass" 的翻譯。改說「經由」或「藉由」
For bytewise (rankwise) math inside a 64-bit register, H
is 0x8080808080808080
and L
is 0x0101010101010101
.
詳細步驟:
x &~H
和 y &~H
去除了每個 byte 的最高位(sign bit),留下其餘 7 位來進行無進位加法。x ^ y
會找出 x
和 y
在每個 byte 中不相同的位元(即可能產生進位的位置)。然後與 H
進行 AND 操作來確保只保留最高位,透過這個操作,我們只保留了那些可能進位的位置。:H
= 1000 0000
x
= 0000 0001
y
= 1111 1110
計算非進位和:
~H
= 0111 1111
x &~H
= 0000 0001
y &~H
= 0111 1110
(x &~H) + (y &~H)
= 0111 1111
處理進位:
x^y
= 1111 1111
(x^y) & H
= 1000 0000
最終結果合併:
((x &~H) + (y &~H)) ^ ((x ^ y) & H)
= 1111 1111
本程式預期可以在一段 string 中找到一個字元,並印出該字元後的 string。
預期執行結果:
X
是否與 long
的邊界對齊。如果 X
沒有對齊,則返回非零值。為什麼要檢查記憶體是否對齊?
memcpy
、memset
等)時,處理對齊問題可以使這些操作更加高效。避免濫用「優化」ㄧ詞,務必詳閱 https://hackmd.io/@sysprog/it-vocabulary
long
型別的大小(通常為 4 或 8 Byte)。long
型別的大小。一開始不明白為何可以運用 0x0101010101010101
和 0x8080808080808080
的邏輯運算,進而判斷是否 DETECT NULL。
直到我看到你所不知道的 C 語言:數值系統篇,
由已知的計算方式來逆推作者可能的思考流程。
首先先將計算再簡化一點點,將他從 (((X) - 0x01010101) & ~(X) & 0x80808080)
變成
將以前學過的笛摩根定理套用上去,於是這個式子就變成了
再稍微調整一下順序
所以就可以進行分析
X | ~(X - 0x01)
=> 取得最低位元是否為 0,並將其他位元設為 1
X
= 0000 0011
=> 1111 1111
X
= 0000 0010
=> 1111 1110
0x80
是什麼? 0x80
是 1000 0000
,也就是 1-byte 的最高位元上面這兩組組合起來,我們可以得到以下結果
X
= 0 => 1000 0000
=> 0x80X
= 1 => 0000 0000
=> 0X
= 2 => 0000 0000
=> 0X
= 255 => 0000 0000
=> 0於是我們得知原來這樣的運算,如果一個 byte 是 0,那經由這個運算得到的結果會是 0x80
,反之為 0。
再將這個想法擴展到 32 位元,在 32 位元的情況下,0 會得到 0x80808080
。
X
是否包含指定的字串,利用 XOR 操作,目的是將 X
中所有與 mask
相同的位元組變為 0。long
型別大小逐塊處理:建立 mask
,將 d
字串複製到 long
的每個位元組中。第三行的 mask
= 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0010 1110 0010 1110
第四行的 mask
= 0000 0000 0000 0000 0000 0000 0000 0000 0010 1110 0010 1110 0010 1110 0010 1110
第六行的 mask
= 0010 1110 0010 1110 0010 1110 0010 1110 0010 1110 0010 1110 0010 1110 0010 1110
LBLOCKSIZE
通常是 8,因此 LBLOCKSIZE * 8
為 64。所以迴圈實際上只執行了一次,我認為這裡不需要使用 for
迴圈也可以達到一樣的效果。事實上在進入 for
迴圈前 d
字串 已經完成複製至後 32 位元的 mask
中了,僅需寫一行 mask |= mask << 32;
即可。但是 for
迴圈設計為能夠靈活應對不同的 LBLOCKSIZE
或其他位寬度環境。
設計實驗並分析產生的組合語言指令序列。
不用抄寫題目,也就是 AAAA, BBBB, CCC 這些填空應予以消除,專注在程式碼本身。
memchr_opt
,設計實驗來觀察隨著字串長度和特定 pattern 變化的效能影響clock_gettime
)記錄每次函數運行的時間。measure_memchr
和 measure_memchr_opt
:分別測量 memchr
和 memchr_opt
的執行時間。
run_experiment
:測試不同長度的字串,並在不同位置測試字母的存在。使用多次運行來獲取平均執行時間。其中 void * memset ( void * str , int c , size_t n )
函式將指定的值 c
複製到 str
所指向的記憶體區域的前 n
個位元組中,這可以用於將記憶體區塊清除或設定為特定值。
注意書寫規範:
main
:呼叫 run_experiment
,測試不同的字串長度。
輸出:
使用 gnuplot 製圖
已補上,但是時間(y 軸)相差太大,待修改。
從結果中可以看到,大部分情況下,memchr
的運行時間比 memchr_opt
短,這可能是由於以下幾個原因:
memchr_opt
在開始時進行了額外的對齊檢查和預處理步驟,這增加了開銷。memchr_opt
進行了對齊檢查,這會導致在字串長度較小或未對齊時,額外的開銷可能超過了任何潛在的優化收益。當字串較短時,對齊檢查和其他操作可能反而降低了效能。memchr_opt
中的條件分支較多,例如對齊檢查和字串長度檢查,可能會導致分支預測失敗,這會影響效能。memchr_opt
利用了 SIMD 技術進行批量處理,這可能在更大資料塊下顯示出優勢,但在較小資料塊下,這些優化的開銷可能反而導致性能下降。Linux 核心原始程式碼找出 x86 對應的最佳化實作:arch/x86/lib/string_32.c
注意上方程式碼距離「最佳化」還很遠,你應該解釋其可改進之處。
repne scasb
指令進行快速字串掃描。memchr_opt
實作策略unsigned long
)進行處理,以加快速度。DETECT_CHAR
巨集進行高效比對。x86 最佳化實作:利用了 x86 指令集的特性,通過 inline assembly 直接操作 CPU 指令來提高效能,適合 x86 平台,簡單高效。
memchr_opt
實作:使用通用的 C 語言進行優化 ,具有跨平台的可移植性,但在特定平台上的性能可能不如專門針對該平台進行優化的實作。
包含延伸問題
注意用語。
優化 64 位元除法的效能,尤其是當除數是常數時。透過在編譯時進行倒數計算,這些巨集和函式能夠顯著減少運行時的計算負擔,從而提升效能。
likely
巨集!!
將 x 轉換為布林值,並使用 GCC 的內建函數 __builtin_expect
,告訴編譯器 x
的值很可能為真,以便進行更好的分支預測和為什麼要使用 __builtin_expect
函數?它究竟有什麼作用呢?
__builtin_expect
函數來增加條件分支預測的準確性,CPU 會提前裝載後面的指令,遇到條件轉移指令時會提前預測並裝載某個分支的指令。編譯器會產生對應的程式碼來優化 CPU 執行效率。n
是 2 的冪,則它的二進位表示中只有一個位為 1,n & (n - 1) 應該等於 0。x
的二的對數__builtin_clz
計算前導零的數量,再根據位元數進行轉換。__builtin_clz
這個函數作用是傳回輸入數二進位表示:從最高位元開始 ( 左起 ) 連續的 0 的個數;如果傳入 0 則行為未定義。
三個不同的函數 分別用於 unsigned int 、 unsigned long 以及 unsigned long long。
來源: Other Built-in Functions Provided by GCC
__builtin_constant_p
是編譯器 GCC 內建函數,判斷 n 在 compiler time 是否為常數,是的話就 return 1
並使用 (n) < 2 ? 0 : 63 - __builtin_clzll(n)
,否則 return 0
並使用 (sizeof(n) <= 4) ? __ilog2_u32(n) : __ilog2_u64(n)
,但根據 GCC 手冊,這不代表 n
不是常數,只是 GCC 無法證明它是在活躍的優化選項集限制內的常數。The function returns the integer 1 if the argument is known to be a compile-time constant and 0 if it is not known to be a compile-time constant. A return of 0 does not indicate that the value is not a constant, but merely that GCC cannot prove it is a constant within the constraints of the active set of optimization options.
在活躍的優化選項集限制內的常數 ( a constant within the constraints of the active set of optimization options ) 是什麼?
使用本課程教材規範的術語。
優化 選項的影響
不同的優化選項會影響編譯器如何判斷一個值是否可以被視為常數。例如,某些優化選項可能允許編譯器進行更積極的 constant folding,這是一種在編譯期間計算表達式結果,並用該結果替換表達式的技術。
利用 gcc __builtin_constant_p
協助於編譯時期進行 Constant folding (propagation),於你所不知道的 C 語言:編譯器和最佳化原理篇介紹過其觀念。表示當 __builtin_constant_p
用於 macro 或 inline function 時,傳入的參數為 常數數值,當開啟編譯器最佳化 -O
選項後即會進行 constant folding。
This approach multiplies by the reciprocal of b: to divide n by b, we multiply n by ( p / b ) and then divide by p.
= n
因為乘法運算比除法運算在 CPU 上執行更快,它需要更少的 CPU 週期和更少的指令數量。透過事先計算除數的倒數,使得除法轉換為乘法,從而提高運算效率。
用第一手材料來說明上方陳述,避免人云亦云。
The efficiency comes from the compiler's ability to optimize much of this code during compile time through constant propagation, leaving behind only a few multiplication operations. This is why we use this large macro, as a static inline function does not consistently achieve the same optimization.
編譯器在編譯期間能夠透過 constant propagation 來優化這段程式碼,最終只留下少量的乘法操作,因此提高運算效率。constant propagation:一種編譯器優化技術,編譯器會分析程式碼中的常數,並將這些常數值直接替換到程式碼中的變數,從而減少運行時的計算量。
為什麼巨集可以達到更好的 constant propagation 效果?而 inline function 不行?
As the C preprocessor can be invoked separately from the compiler with which it is supplied, it can be used separately, on different languages.
Notable examples include its use in the now-deprecated imake system and for preprocessing Fortran.
原來 C preprocessor 以獨立程式的形式存在,所以當我們用 gcc 或 cl (Microsoft 開發工具裡頭的 C 編譯器) 編譯給定的 C 程式時,會呼叫 cpp (伴隨在 gcc 專案的 C preprocessor) 一類的程式,先行展開巨集 (macro) 或施加條件編譯等操作,再來 才會出動真正的 C 語言編譯器 (在 gcc 中叫做 cc1)。
主要原因是巨集在階段進行文本替換,使得編譯器能夠更早、更靈活地對展開的程式碼進行優化。而 inline function 仍然保留了函數的特性和約束,優化效果可能受到限制。因此,在需要充分利用 constant propagation 進行優化的情況下,巨集通常比 inline function 更有效。
___p
___p
:___b
的最高有效位。
___m
,使其接近 ((p << 64) + b - 1) / b
的值想法:
(p << 64) / b
:將 p 264 除以 b
。(p << 64 / b) + (b - 1) / b
:為了進一步調整結果,使得 m
更接近 (p << 64) / b
。這樣可以確保在進行後續的計算時,誤差最小化。實際寫法:
~0ULL
: 0xffffffffffffffff
,是一個全為 1 的 64 位元無符號整數,接近 264。___m = (~0ULL / ___b) * ___p
:可視為 (264 / b) p,將 p
調換位置到最前面就可得 (p 264) / b = (p << 64) / b
。(p << 64) / b
時,目的是得到一個非常大的數字,使得在後續的除法操作中結果更準確。直接計算 (p << 64) / b
可能會因為整數除法而產生誤差。(b - 1) / b
的部分,就可以達到接近 ((p << 64) + b - 1) / b
的值。(~0ULL % ___b + 1) * ___p)
,目的是增加精度,這樣除以 ___b
時結果更加接近實際值。數學證明:
註解想表達的是:
但是程式碼硬生生多出了 (~0ULL % ___b + 1) * ___p)
,看似不必要的片段,所以我們可以透過除法公式分析。
根據除法公式:
其中 為被除數、 為除數、 為商數、為餘數。
兩邊同時加一令:
根據註解可知:
展開可得:
最後通分:
其中:
= ~0ULL /
= ~0ULL %
透過觀摩 yy214123 同學的期末專題,才了解這一段程式碼的意義。
透過計算最大無符號整數 (~0ULL
) 可以整除 ___b
的最大值,然後減去 1,得到一個略小於最大無符號整數,且能夠使除法運算結果最大化的被除數。
目的:在進行大數除法時,可以用來檢查和處理極限情況。
= - 1
res = m * x / (p << 64)
檢查 ___m
是否在數學上正確表達了近似值 (p << 64) / b
計算 ___m
和 ___x
,模擬 64 位數字相乘的低位部分。
計算 ___m
的低 32 位和 ___x
的高 32 位相乘,並加到 ___res
中,更新 ___res
的值,同時將這個中間結果保存到 ___t
中。
計算 ___x
的低 32 位和 ___m
的高 32 位相乘,並加到 ___res
中,進一步更新 ___res
的值。
檢查 ___res
是否小於 ___t
,如果是,代表有溢出情況,則 ___t
設置為 1ULL << 32
,否則設置為 0
。附註:因為 ___res
越加越小是不合理的,所以 ___res
< ___t
一定是有溢出情況。
___res
右移 32 位,並加上 ___t
,這樣可以得到乘法結果的高 32 位部分。
計算 ___m
和 ___x
的高 32 位相乘,並加到 ___res
中,得到整個 64 位乘法結果的最終值。最後將 ___res
除以 ___p
,得到最終的結果。
if (~0ULL % (___b / (___b & -___b)) == 0)
:檢查 ___b
是否為 2 的冪。
___b & -___b
:用來找出 ___b
的最低有效位(LSB)。
-___b
等價於 (~___b + 1)
,即將 ___b
取反後再加 1。___b = 6
,其二進位表示為 0000 0110
。-___b = 1111 1010
。___b & -___b = 0000 0010
,即 2
。當 ___b
是 2 的冪時,___b
只有一個位元為 1,其餘位元全為 0。例如:
這時,___b & -___b
會等於 ___b
本身。假設 ___b = 8
:
___b = 0000 1000
-___b = 1111 1000
(取反加 1)___b & -___b = 0000 1000
所以 ___b & -___b = 8
,___b / (___b & -___b) = 8 / 8 = 1
。如果 ___b
是 2 的冪,則 ___b / (___b & -___b)
的結果是 1,然後 ~0ULL % 1 == 0
。這是因為任何數對 1 取餘數都等於 0。
由於 ___b & -___b
會等於 ___b
本身且 ___b / (___b & -___b)
的結果是 1,因此上面的程式碼中的變數可轉換為:
這裡我不理解,因為 if (~0ULL % (___b / (___b & -___b)) == 0)
這個情況只發生在 ___b
是 2 的冪時成立,但是如果___b
是 2 的冪並不會進入此巨集。
if (~0ULL % (___b / (___b & -___b)) == 0)
有可能在其他情況下成立嗎?
else if (___res != ___x / ___b)
,說明在透過乘法和位移運算來近似除法的過程中,出現了由於有限精度和位截斷引起的誤差。
It is necessary to introduce a bias to counteract errors caused by bit truncation.
位元截斷就是當我們處理浮點數或大數進行除法、乘法等運算時,計算結果可能超出變數類型所能表示的範圍。為了解決這個問題,我們通常會截斷多餘的位元,這可能導致數值誤差。為了減少這些誤差,需要引入一個偏差這個偏差是一個額外的數值,用來調整計算結果,使其更接近真實值。
Without this bias, an extra bit would be required to accurately represent the variable 'm', which would exceed the capacity of a 64-bit variable. To manage this, the solution involves setting 'm' equal to 'p' divided by 'b', and 'n' divided by 'b' as equal to (n * m + m) / p. This approach is taken because avoiding the bias is not feasible due to the limitations imposed by bit truncation errors and the 64-bit variable size constraint.
如果沒有引入偏差,我們需要額外的位元來表示計算過程中的變數 m
。
解決方案:將變數 m
設定為 ,這樣可以確保 m
的值在 64 位元變數的範圍內。同時,我們將變數 設為 ,這樣的設置能夠在計算過程中保持結果的準確性。
= m = =
Question: 目前不理解為什麼在變數 m
、 p
、 b
沒有改變的情況下,這裡需要再計算一次變數 m
? 使m = (p << 64) / b
。
原本計算過一次 (m = ((p << 64) + b - 1) / b)
,差異是 (b - 1)
。
原本:
現在:
Answer: 重新計算 ___m
的必要性在於如果 ___res
和 ___x / ___b
不相等,說明前面的近似計算存在較大的誤差,必須重新計算 ___m
以提高精度。
上方答覆過於武斷,要用數學分析!
TODO:帶入數值分析
在一般情況下,減少 m
和 p
,並嘗試清除 m
的第 31 位,否則需要額外的溢出處理。
___m & -___m
會得到 ___m
中最低位的 1。對此值取負數,會將這個最低位的 1 變為最高位的 1,其他位為 0。例如,如果 ___m = 0000 1000
,則 ___m & -___m = 0000 0000 0000 1000
,取負後為 1111 1111 1110 0000
。
透過將 ___m
右移32位,得到 ___m
的高 32 位,再與 ___bits
進行 or,將高 32 位加入到 ___bits
。
透過取反操作,得到的結果是 ___bits
中所有 0 變 1,1 變 0,這使得高位為 0 而低位為 1。左移一位將最低位變為最高位,表示可以應用的最佳減少量。
~___bits
:對 ___bits
取反。例如,對於 1111 1111 1110 0000
,這會產生 0000 0000 0001 1111
。
<< 1
:將結果左移一位。例如,對於 0000 0000 0001 1111
,這會產生 0000 0000 0011 1110
。
最佳減少量
~___bits
左移一位後,我們得到了一個包含 1 的數字,這些 1 位表示我們可以安全地減少的位數。目的
計算出一個最佳的減少量來減少 ___m
和 ___p
,從而避免溢出並提高計算效率。
當 ___bits
為 0 時,我們無法避免設置第 31 位,在這種情況下,我們應用最大可能的減少量。
如果 ___bits
為 0
___m
的第 31 位為 1。在這種情況下,直接將 ___m
和 ___p
除以 ___m
中最低有效位的 1,以最大可能地減少 ___m
和 ___p
。
___bits
不為 0根據 ___bits
的 MSB,決定應用多少位的減少量。使用 ilog2(___bits)
計算 ___bits
中最高有效位的位數。
Now we have a combination of 2 conditions:
Condition 1 : whether or not we need to apply a bias.
Condition 2 : whether or not there might be an overflow in the cross product determined by(___m & ((1 << 63) | (1 << 31)))
.
Select the best way to do(m_bias + m * n) / (1 << 64)
.
From now on there will be actual runtime code generated.
__xprod_64
函式中判斷是否需要偏差來抵消因位元截斷造成的誤差。__xprod_64
函式檢查 (___m & ((1 << 63) | (1 << 31)))
來確定是否會發生溢位。1 << 63
和 1 << 31
分別是第 63 位和第 31 位。___m
的第 63 位和第 31 位被設置,可能會導致乘法運算中的溢位問題。根據這兩個條件,選擇最佳的計算方法來進行 (m_bias + m * n) / (1 << 64)
的運算。這裡 m_bias
是偏差,m
是乘數,n
是被除數的一部分。
__xprod_64
會根據是否應用偏差來決定如何進行運算,以避免溢位並保證結果的準確性。
___p
為 2 的冪,用於將結果進一步調整到正確的範圍內。
__xprod_64
計算乘積retval = ((bias ? m : 0) + m * n) >> 64
根據是否應用偏差進行條件判斷:
Case 1:不需要偏差。
Case 2:需要偏差,不會發生溢位。
Case 3:需要偏差,可能會發生溢位。
計算 m_lo * n_hi
和 m_hi * n_lo
,並將結果右移 32 位。
Case 1:不會發生溢位。
Case 2:可能會發生溢位。
加上 m_hi * n_hi
的乘積,最終結果存儲在 res
中並返回。
__rem
。__base
是常數且是 2 的冪。__base
是常數且不為 0。__base
不是編譯時常數,並且 n
小於 232。__base
不是編譯時常數,並且 n
大於 232 。補充:__builtin_constant_p(__base)
會是 false 的情況
res
進行除法運算,並輸出商數。uint64_t r = div64(res, 3);
,則 r
為餘數。argc
的值argc
是命令行參數的數量。當執行程式時,命令行的每一個參數都會被計算在內,包括程式的名稱。./program arg1 arg2
,則 argc
的值為 3(包括程式名和兩個參數)。因此,argc
的值取決於如何執行程式,它不是每次都固定的。./program
執行程式(即沒有額外的參數),argc
是 1。Linux 核心原始程式碼:include/asm-generic/div64.h
探討 do_div()
巨集在不同除數情境下的表現。
步驟1:撰寫測試模組
撰寫一個 Linux 核心模組,實現以下功能:
BITS_PER_LONG == 32
,可以分成以下四個情境討論。do_div()
的執行時間和結果。使用 clang-format 確認書寫風格一致
在這次作業內新增一個檔名為 .clang-format 的文件,複製 lab0 檔案中的 .clang-format 文件內容並貼上。使用指令clang-format -i *.[ch]
即可調整作業程式要求的風格。
參照本學期的課程教材!不要捨近求遠。
為何不看?
步驟2:編譯並載入模組
編寫 Makefile 以編譯模組:
編譯模組:
載入模組:
查看核心訊息以獲取測試結果:
do_div()
的執行時間當除數是常數:對於 2 的冪的除數,編譯器可以使用右移操作來代替除法操作(例如 n >> 3
來代替 n / 8
),這比實際的除法運算要快得多。case 2 中,雖然除數不是 2 的冪,但由於除數是常數,編譯器可以進行一些數學優化,使得除法運算更高效。
當除數不是常數:
無法使用位元運算或乘法反元素等編譯時期優化技術,必須在運行時進行實際的除法運算,導致運行時間較長。當 n
的值較大且 base
不是常數時,除法運算需要進行完整的 64 位整數除法,這是一個相對複雜且耗時的運算過程,因此運行時間最長。
在不同 case 下的效益和限制
case 1:常數除數且為 2 的冪
效能:右移運算比標準除法運算快很多,因為位運算在硬體上直接支持且執行速度快。
限制:僅適用於除數為 2 的次方的情況,其他除數不適用。
case 2:常數除數但不是 2 的冪
效能:編譯期可以進行一些優化,例如使用乘法和位移來模擬除法,從而提升運行速度。
限制:僅在除數為常數的情況下有效,變量除數無法應用這種優化。
case 3:除數不是編譯時常數,且被除數小於 232
效能:直接使用 32 位的除法運算,效率相對較高。
限制:僅適用於 n
可以用 32 位表示的情況,對於更大的數值無法應用。
case 4:除數不是編譯時常數,且被除數大於 232
效能:需要進行 64 位的除法運算,並且在運行時動態計算,速度較慢。
限制:無法進行編譯時期優化,完全依賴於運行時的計算,效能較低。
Linux 核心專題:位元運算的應用
Linux 核心專題:重作第四次作業
Linux 核心專題:位元操作的應用
Linux 核心專題:評估位元操作的效益
Linux 核心專題:workqueue 研究