linux2022
contributed by < blue76815
>
參考資料:
1
EXP1
= a & b & 1
EXP2
= a & b
EXP3
= a ^ b
延伸問題:
&
代表 AND
|
代表 OR
^
代表 XOR
!
代表 NOTAND 運算
OR 運算
XOR 運算
NOT 運算
EXP1
>> 1
相當於 ,
例如 ,
則
EXP1
式子,一開始我寫成其中 & 0x01
是為了只讀 bit 0 值
以 為例,則 (a & 0x01)
以 為例,則 (b & 0x01)
相當於
EXP1
式子再簡化成
相當於
EXP2
和 EXP3
在你所不知道的 C 語言:數值系統篇-運用 bit-wise operator 裡面有提到
- 避免 overflow
- 比方說
(x + y) / 2
這樣的運算,有個致命問題在於 (x + y) 可能會導致 overflow (考慮到 x 和 y 都接近 UINT32_MAX,亦即 32-bit 表示範圍的上限之際)- 於是我們可以改寫為以下:
- 用加法器來思考:
x & y
是進位,x ^ y
是位元和,/ 2
是向右移一位- 位元相加不進位的總和:
x ^ y
; 位元相加產生的進位值:(x & y) << 1
x + y = x ^ y + ( x & y ) << 1
- 所以 (x + y) / 2
=(x + y) >> 1
=(x ^ y + (x & y) << 1) >> 1
=(x & y) + ((x ^ y) >> 1)
因此 EXP2
和 EXP3
寫成
參考資料:
在 CS.APP CH3.4 訪問信息 章節提到
%ax
-%sp
暫存器%eax
到%esp
%rax
到%rsp
%r8
到%r15
X86 暫存器表列
64-bit模式 | 32-bit模式 | 16-bit模式 | 8-bit模式 | 用途 |
---|---|---|---|---|
%rax | %eax | %ax | %al | 返回值 |
%rbx | %ebx | %bx | %bl | 被調用者保存 |
%rcx | %ecx | %cx | %cl | 第4個參數 |
%rdx | %edx | %dx | %dl | 第3個參數 |
%rsi | %esi | %si | %sil | 第2個參數 |
%rdi | %edi | %di | %dil | 第1個參數 |
%rbp | %ebp | %bp | %bpl | 被調用者保存 |
%rsp | %esp | %sp | %spl | stack pointer |
%r8 | %r8d | %r8w | %r8b | 第5個參數 |
%r9 | %r9d | %r9w | %r9b | 第6個參數 |
%r10 | %r10d | %r10w | %r10b | 調用者保存 |
%r11 | %r11d | %r11w | %r11b | 調用者保存 |
%r12 | %r12d | %r12w | %r12b | 被調用者保存 |
%r13 | %r13d | %r13w | %r13b | 被調用者保存 |
%r14 | %r14d | %r14w | %r14b | 被調用者保存 |
%r15 | %r15d | %r15w | %r15b | 被調用者保存 |
通用暫存器(GPR) - 32位元命名約定
8個GPR是:
- 累加器暫存器(AX)。用在算術運算。
- 基址暫存器(BX)。作為一個指向資料的指標(在分段模式下,位於段暫存器DS)。
- 計數器暫存器(CX)。用於移位/迴圈指令和迴圈。
- 資料暫存器(DX)。用在算術運算和I/O操作。
- 堆疊指標暫存器(SP)。用於指向堆疊的頂部。
- 棧基址指標暫存器(BP)。用於指向堆疊的底部。
- 源變址暫存器(SI)。在流操作中用作源的一個指標。
- 目標索引暫存器(DI)。用作在流操作中指向目標的指標。
將它們以這樣的順序列出是有原因的:
這個順序和堆疊操作中推入棧中的次序相同,我們以後會講到。所有暫存器都可以在16位元和32位元模式下被存取。
- 在16位元模式下,通過上面的列表中兩個字母的縮寫來確定該暫存器。
- 在32位元模式下,這兩個字母的縮寫名字前有「E」(extended,延伸)。例如,「EAX'是累加器暫存器作為一個32位元的值。
- 類似地,在64位元的版本,「E」被替換為「R」,所以在64位元版本「EAX'被稱為'RAX'。
S:source
D:destination
指令 | 效果 | 描述 |
---|---|---|
MOV S,D | D <– S | 搬運資料存到 D |
leaq S,D | D <– &S | 加載有效地址 |
INC D | D <– D+1 | +1 |
DEC D | D <– D-1 | -1 |
NEG D | D <– -D | 取負數 |
NOT D | D <– ~D | 取補數 |
ADD S,D | D <– D+S | 加 |
SUB S,D | D <– D-S | 減 |
IMUL S,D | D <– D*S |
乘 |
XOR S,D | D <– D ^ S | XOR 運算 |
OR S,D | D <– D or S | OR 運算 |
AND S,D | D <– D & S | AND 運算 |
SAL k,D | D <– D << k | 左移k bit |
SHL k,D | D <– D << k | 左移k bit (效果和SAL 一樣) |
SAR k,D | D <– D >> k | 算術右移k bit(bit空缺補上sign integer) |
SHR k,D | D <– D >> k | 邏輯右移k bit(bit空缺補上0) |
average.c
使用 -O3 最佳化
$ gcc -O3 -S average.c
編譯成組語
average.c
用以下3種寫法編譯看組語內容
生成的average.s
內容為
在 .cfi_* 汇编指示符 提到
- CFI 即 Call Frame Information,是 DWARF 2.0 定義的函數 stack信息,DWARF 即 Debugging With Attributed Record Formats ,是一種調試信息格式。
- 組語經常看到 .cfi_ 開頭的組語指示符,
例如.cfi_startproc
、.cfi_undefined
、.cfi_endproc
等,- CFI 即 Call Frame Information,是 DWARF 2.0 定義的函數棧信息,
- DWARF 即 Debugging With Attributed Record Formats ,是一種調試信息格式。
.cfi_
開頭的組語指示符用來告訴彙編器生成相應的 DWARF 調試信息,主要是和函數有關。.cfi_startproc
定義函數開始,.cfi_endproc
定義函數結束。
因為我們的函式是 uint32_t average(uint32_t a, uint32_t b)
uint32_t
,所以組語編譯成 32-bit 模式uint32_t a
為第1個輸入變數,所以暫存器 %edi
存 a 變數uint32_t b
為第2個輸入變數,所以暫存器 %esi
存 b 變數對應用到的 X86 暫存器為
32-bit模式 | 用途 |
---|---|
%eax | 返回值 |
%ebx | 被調用者保存 |
%esi | 第2個參數(存 b 變數) |
%edi | 第1個參數(存 a 變數) |
%edx | 第3個參數 |
所以 case1 生成的average.s
內容為
生成的average.s
內容為
生成的average.s
內容為
%esi:存第2個參數 b
case3 編譯若使用 gcc -O0
不做任何最佳化
$ gcc -O0 -S average.c
生成的average.s
內容為
其中
7.10.9
.cfi_def_cfa register, offset
.cfi_def_cfa
defines a rule for computing CFA as: take address from register and add offset to it.7.10.11
.cfi_def_cfa_offset offset
.cfi_def_cfa_offset
modifies a rule for computing CFA. Register remains the same, but offset is new. Note that it is the absolute offset that will be added to a defined register to compute CFA address.7.10.13
.cfi_offset register, offset
Previous value of register is saved at offset offset from CFA.
介紹篇幅太長,另開一個共筆
quiz2 測驗1 EWMA 介紹
在 linux kernel 中搜尋 DECLARE_EWMA
drivers/net/wireless/mediatek/mt76/mt76x02_mac.h
依照 DECLARE_EWMA()
定義
#define DECLARE_EWMA(name, _precision, _weight_rcp)
其中3個輸入參數定義
name
: 表示整個 macro 定義出來的一組 macro 以及 struct 的名稱。_percision
: 表示用多少個 bits 來表示內部儲存的小數部分。_weight_rcp
: 表示權重 的倒數, 。
DECLARE_EWMA(pktlen, 8, 8);
代表
name
: pktlen_percision
: 表示用 8 bits 來表示內部儲存的小數部分。_weight_rcp
: 表示權重 的倒數, 。drivers/net/wireless/mediatek/mt7601u/mt7601u.h
DECLARE_EWMA(rssi, 10, 4);
代表
name
: rssi_percision
: 表示用 10 bits 來表示內部儲存的小數部分。_weight_rcp
: 表示權重 的倒數, 。drivers/net/wireless/realtek/rtw88/main.h
DECLARE_EWMA(tp, 10, 2);
代表
name
: tp_percision
: 表示用 10 bits 來表示內部儲存的小數部分。_weight_rcp
: 表示權重 的倒數, 。DECLARE_EWMA(rssi, 10, 16);
代表
name
: rssi_percision
: 表示用 10 bits 來表示內部儲存的小數部分。_weight_rcp
: 表示權重 的倒數, 。2
EXP4
= a ^ b
EXP5
= a < b
延伸問題:
請在 Linux 核心原始程式碼中,找出更多類似的實作手法。請善用 git log
檢索。
如何製作無分支代碼?
在上面的答案中,提到瞭如何通過避免分支來避免分支預測失敗.
branchless 參考資料:
git log
檢索。參考 在 Linux 核心原始程式碼中,找出更多類似的實作手法
git log
檢索方法
--oneline
選項將每一筆提交顯示成單獨一行,對於檢視大量的提交時很有
--name-only
在提交訊息後方顯示更動的檔案列表。
--grep
列出提交訊息中符合指定字串的提交。
$ git log --oneline --name-only -E --grep "branchless | branch-free"
xfrm: branchless addr4_match() on 64-bit
powerpc/ebpf32: Rework 64 bits shifts to avoid tests and branches
KVM: MMU: simplify last_pte_bitmap
KVM: MMU: Optimize pte permission checks
3
COND
= v
RET
= u << shift
延伸問題:
複習數學求最大公因式時,用到輾轉相除法
根據 kevinshieh0225 同學介紹
從
上面這段
用到輾轉相除法,但是 % 取餘數是很承重的運算
變成以下等價
1.修改演算法,避開% 取餘數的使用
最後總結成
4
EXP6
= bitset & -bitset
延伸問題:
在 Bit Twiddling Hacks 解析 (三) 中有提到
實驗設定
每種 bit density測試時,分別傳送N筆隨機位置的bitmap data,
傳到 naive()
和 improved()
比較耗時差別
bit-desity 從 1個bit為1,但是在64bit 中位置隨機,餵 N 筆資料
傳到 naive()
和 improved()
比較耗時差別
一直實驗運算到
bit-desity 64個bit為1,但是在64bit 中位置隨機,餵 N 筆資料
傳到 naive()
和 improved()
比較耗時差別
程式碼 c2ecb0e
makefile中,已寫好 make plot
,能自動化執行
10000筆,50000筆,100000筆資料量測和 gunplot 繪圖
實驗結果
c2ecb0e 程式碼
將 swap(&array[i], &array[rand_i]);
注記掉不做洗牌
使得bitmap 變數為連續1,而不是隨機位置1擺放
bitmap 變數若為連續1的位置擺放
則隨著 bit density 提高,就會出現前面同學實驗的情況
例如 vacantron同學
可以發現當 bitmap density 越小時 ( 1 越稀疏時),修改過後 (使用
builtin_ctzl()
) 的程式明顯比其他還有效率,隨著 bitmap 的 1 增加,builtin_ctzl()
的影響力相對的就下降了。
例如 tinyynoob 同學
若 bitmap 越鬆散 (即 1 越少),於是 improved 的效益就更高。
呼應
若 bitmap 越鬆散 (即
1 越少,1擺放的位置越分散隨機),於是 improved 的效益就更高。
用perf分析
uint64_t make_bitmap(int set_bit_num)
內有加入
swap(&array[i], &array[rand_i]);
洗牌時
uint64_t make_bitmap(int set_bit_num)
內沒加入
swap(&array[i], &array[rand_i]);
洗牌時,為連續1擺放時
用 perf 分析記憶體或是反組譯組語看有啥地方被最佳化
左側有+的地方,鍵盤按enter
鍵可展開分支
先輸入
$ perf top list
看 perf top
指令有哪些設定參數
perf top 使用方法
參考 freshLiver(quiz2 測驗3 的perf分析步驟)
根據 perf stat 指令介紹
我使用
$ sudo taskset -c 0 perf stat -r 1 ./main 100000 > data.csv
其中 -r
代表 repeat 重複執行的次數,因為我的程式./main 100000
本身餵10000筆資料時,就有在重複運算10000次
因此只設定perf stat -r 1
只有repeat 重複執行1次
uint64_t make_bitmap(int set_bit_num)
內有加入swap(&array[i], &array[rand_i]);
洗牌時uint64_t make_bitmap(int set_bit_num)
內沒加入swap(&array[i], &array[rand_i]);
洗牌時,為連續1擺放時perf stat 比較有明顯差異的數據是在
swap(&array[i], &array[rand_i]);
洗牌時swap(&array[i], &array[rand_i]);
洗牌時沒洗牌時 (1連續擺放,較密集的情況下)
branches 和 branch-misses 數據較低,但是解釋不出來,為何在 improved() 函數執行時,運算效能卻比有洗牌時(1隨機位置擺放)還低
5
PPP
= pos--
MMM
= list_add
EEE
= &heads[remainder % size]
延伸問題:
例如,判斷負號只要寫作
bool isNegative = numerator < 0 ^ denominator < 0;
搭配研讀 The simple math behind decimal-binary conversion algorithms
mm/
目錄 (即記憶體管理) 中,找到特定整數比值 (即 fraction) 和 bitwise 操作的程式碼案例,予以探討和解說其應用場景6
M
= _h
X
= 0
延伸問題:
__alignof__
的使用案例 2 則,並針對其場景進行解說ALIGN
, ALIGN_DOWN
, ALIGN_UP
等巨集,探討其實作機制和用途,並舉例探討 (可和上述第二點的案例重複)。思索能否對 Linux 核心提交貢獻,儘量共用相同的巨集
alignof
為 C++11 新增的關鍵字,當作運算子使用,回傳物件對齊所需的位元組數。用法如下註: 在 64 位元 (bit) 的機器上,處理器抓一次資料就是 64 位元,而 64 位元等於 8 個位元組 (byte) ,各種型態卻不會剛好都是 8 個位元組,因此就有放置資料的對齊 (alignment) 問題。
根據 Learn a new trick with the offsetof() macro
Listing 1: A representative set of offsetof() macro definitions
// Keil 8051 compiler
#define offsetof(s,m) (size_t)&(((s *)0)->m)
To better understand the magic of the offsetof() macro, consider the details of Keil's definition. The various operators within the macro are evaluated in an order such that the following steps are performed:
((s *)0)
takes the integer zero and casts it as a pointer to s .((s *)0)->m
dereferences that pointer to point to structure member m .&(((s *)0)->m)
computes the address of m .(size_t)&(((s *)0)->m)
casts the result to an appropriate data type.
依此類推,#define ALIGNOF(t)
拆步驟解析
(struct { char c; t _h; } *)0)
:將 struct { char c; t _h; }
設成指標型態 *
,並將指標位址設為0
((struct { char c; t _h; } *)0)->_h
:取得該 struct pointer中的 _h
成員
&((struct { char c; t _h; } *)0)->_h
:加&
取得 _h
成員記憶體位址
(char *)(&((struct { char c; t _h; } *)0)->_h)
:將 _h
成員記憶體位址 強制轉型成 (char *)
型態
參照 The (char *) casting in container_of() macro in linux kernel
((char *)(&((struct { char c; t _h; } *)0)->_h) - (char *)0)
:和(char *)0
位址相減後,即為 t _h
的 alignment
我卡在 步驟4
將 _h
成員記憶體位址 強制轉型成 (char *)
型態,為何就能轉換出 ALIGNOF 差距的記憶體格數?
__alignof__
的使用案例 2 則,並針對其場景進行解說
__attribute__((aligned(n)))
表示所定義的變量為n字節對齊;字節對齊的細節和編譯器實現相關,但一般而言,滿足三個準則:
- (結構體)變量的首地址能夠被其(最寬)基本類型成員的大小所整除;
- 結構體每個成員相對於結構體首地址的偏移量(offset)都是成員大小的整數倍,如有需要編譯器會在成員之間加上填充字節(internal adding);
- 結構體的總大小為結構體最寬基本類型成員大小的整數倍,如有需要編譯器會在最末一個成員之後加上填充字節(trailing padding)。
ALIGN
, ALIGN_DOWN
, ALIGN_UP
等巨集,探討其實作機制和用途,並舉例探討其中在 linux/const.h 可查到
__ALIGN_KERNEL(x, a)
和 __ALIGN_KERNEL_MASK(x, mask)
定義
7
KK1
= div3
KK2
= div5
KK3
= div3 << 2
延伸問題:
naive.c
和 bitwise.c
效能落差
bitwise.c
程式碼,試圖運用更少的指令來實作出 branchless;
kernel/time/
目錄的標頭檔和程式碼)
過程中,你可能會發現可貢獻到 Linux 核心的空間,請充分討論