contributed by <LanKuDot
>
Figure 1
,可以看到 CPU 時脈並沒有隨著電晶體數量而增加,反而是趨緩了[Source]
yield
),去執行其他函式,再次呼叫這個函式時會從上次離開點開始執行。比較令我在意的是那個 case in for loop,所以我寫了小程式驗證
caseInForLoop()
只有呼叫兩次,但實際輸出卻有三次!__s
的值為 0,所以會印出 case 0 的訊息,然後值被改為 1,之後回傳。__s
值為 1,從輸出可以知道他跳到 case 1 開始執行,之後又回到迴圈頭重新執行,所以又會再輸出一次 case 0 的訊息case
是類似 goto
,以 switch
statement 裡面的值決定要從 switch block 裡面的那一個點開始執行,而 break
是跟程式說執行到這裡就好了回到 Jserv 老師的程式,user_thread_1
比較好理解,與測試程式一樣,只有第一次進入時 __s
值為 0,之後在無限迴圈中值都會保持在 21,從 case 21
開始執行,在無限迴圈中印完訊息後,delay、回傳。
至於 user_thread_2
,
__s
為 0,condition
為 1,所以會印出 (1)
,__s
變為 32case 32
開始,印出 (2)
,condition
為 0,__s
變為 36case 36
開始這樣應該就會發現每次再次呼叫函式時,都會從上次的離開點的下一行開始執行,這正是 coroutine 中 yield
的功能。再檢視一次 cr_yield
macro
switch
statement 會從輸入的變數值判斷要從 switch
statment block 中的哪一個部分開始執行。而 case
就是一個標籤,告訴 switch
statment 可以從哪裡開始,break
則是到哪裡結束。case
之間的 scope 除非特別指定 (用 {}
),否則是相通的。見下例:
z
的宣告 (把 case
無視掉可以比較容易理解),編譯就會出現錯誤訊息。
z
的 scope,讓他只適用在一個 case
statement 下:
size_2_bin
負責運算要配置的物件需要從那一塊記憶體去配置。file_align.c
stat
:display file or file system status
$ man 2 stat
可以看到其函式的用法int stat(const char *pathname, struct stat *buf);
struct stat
:是用來儲存檔案資訊的資料結構,包含檔案大小、類型、修改日期等資訊strncpy
:複製 source 的前 n 個 byte 到 destination
<originText>(會有newline)+<serial of \0>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
會取來自 *ptr
直接寫入 size * nmemb
bytes 到檔案中,不會因為有 null character 而中斷寫入
^@
,這是 null charactertext_align.c
或 line_align.c
更能描述這個程式的功能wbuf
是用 malloc
配置的,程式結束時沒有使用 free
rbuf
的內容才會被複製到 wbuf
,問題有二:!= 0
不成立),沒有任何東西被寫到 wbuf
,會造成這筆資料遺失file.c
與 file_align.c
很相似,查看兩者用途發現 file.c
是模組化的版本
debug.h
dprintf
,與 printf
的用法一樣,只是會在前面增加 DEBUG:
的字樣,透過定義 DEBUG
macro 啟用。
dprintf
功能不一樣,c library 的為將輸出寫入指定的 file descriptorphonebook_opt.c
append_a
:主要是用在傳遞給 thread 的訊息之資料結構,以下是修改過後的,更能辨別用途
new_append_a()
改為 createThread_arg()
mmap
:將指定檔案的資料映射到 process 的 virtual memory space 上,可以防止 blocking I/O 的發生。關於
mmap
詳細用法可以參考林哲亘的共筆
munmap
system call 來刪除配給的區塊,不過在程式結束時,這個區塊會自動被 delete 掉。關閉對應的 file descriptor 不會刪除配給的記憶體malloc
system call 所造成的 waiting。pthread_setconcurrency
:Setting the concurrency level allows the application to give the system a hint as to the number of kernel-scheduling entities that should be provided for efficient execution of the application,還不太懂用途open
開啟檔案會配置資源給對應的 file descriptor,要用 close
來釋放資源valgrind
檢查有無 memory leak 的問題。可以發現 malloc 28次,卻只有 13 次 free
THREAD_NUM
是在 compilation 時期指定,則隨 THREAD_NUM
變動的 array 長度可以不用使用 malloc
thread_args
的元素也是透過 malloc
取得,要 free 掉findName()
中會給找到的 entry 的 lastname
和 detail
配給記憶體,但是沒有處理已經被搜尋過的 entry 不需要再配置記憶體的情況,像在主程式中搜尋 zyxel
就有三次,造成前兩次配置的記憶體遺失。
mmap
多加一個 PROT_WRITE
的 flag,直接在映射的記憶體上處理 \n
,而不配置記憶體給他。另外在 append
中取用一個新的 entry 時也會同時初始化 dtl
為 NULL
,讓 findName()
判斷要不要配置新記憶體給他。另一個好處是在釋放 entry 的記憶體時不需要檢查 dtl
是否為 NULL
,根據 manual 如果 free()
收到 NULL
並不會做任何事情。不過我在修改這裡時發生神奇的事情,原先我只加
PROT_WRITE
,會在第一個 assertion (問你有沒有實做 findName 那個) 失敗。將MAP_SHARE
改成MAP_PRIVATE
才正常運作。是因為沒有 sync? (同一個程序去修改需要 sync 嗎?)
valgrind --tool=memcheck --leak-check=yes <executable>
可以更詳細知道哪裡有配置記憶體但沒有 free 的程式碼,還包含 call stack。一個是 pHead
在 main 中被移動到下一個,造成原版的頭遺失。另一個是在 findName()
中配置的 detail 記憶體沒有被一併 free 掉。這時候就要出動 LeakSanitizer,可一併對照 Mozilla Taiwan 的網誌: Address-Sanitizer(ASAN): 一個 C/C++ 記憶體偵錯的工具 jserv
我使用 ASAN 去偵測,但是程式很安靜的結束了,不過我檢查 valgrind 的報表,他標示 5 個 block 是 still reachable,然後查 StackOverflow,提到可以不必擔心這一區塊的 memory,整理到下面。LanKuDot
#ifndef, #else, #endif
)。就好像是要重新粉刷牆面的某一些地方,沒有照著原本牆面的粉刷方向去刷,整面牆就會有各種方向的刷痕。
OPT
的 condition macro,增加可讀性。-fsanitize=address
,編譯器會插入除錯程式碼,再加上 -fno-omit-frame-pointer
可以讓錯誤訊息回報正確的 call stack根據這篇回答:memory leak 有兩種定義
只有第二種才是真正的 memory leak。然而如果 valgrind
回報的區塊是分類在 still reachable 的話 (由於 program 尚在追蹤指向這些區塊的指標),代表這些區塊雖然還沒被 free,但是之後還是可以被系統取回。所以不必擔心被分類在 still reachable 的區塊
原本的版本中 append()
還有兩個地方可以改進:
data
跟 entry
) 卻以 column-major 去存取。這樣會造成大量 cache-miss 和 cache-reference。
所以我做了以下修正:
num_of_data / THREAD_NUM + 1
的資料去處理,+1 是為了避免餘數集中在最後一個 thread。最後一個 thread 的資料結尾則直接指定 data_pool
的結尾。想問這邊
num_of_data / THREAD_NUM + 1
如果無法整除的話該如何處理??
我嘗試過這樣做,但是如果在 words.txt 裡新增一筆資料後會有問題…
littlewhiteYA
改變取址的方式得到了大量的改進,起飛拉
[append() time], [findname() time] in seconds
append()
:0.009584(!!!) 0.010219