contributed by <ChenYi
>
ChenYi
sysprog
Distribution: Ubuntu 16.04 LTS
Architecture: x86_64
CPU 作業模式: 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
每核心執行緒數:2
每通訊端核心數:4
Socket(s): 1
NUMA 節點: 1
供應商識別號: GenuineIntel
CPU 家族: 6
型號: 60
Model name: Intel(R) Xeon(R) CPU E3-1231 v3 @ 3.40GHz
製程: 3
CPU MHz: 3748.632
CPU max MHz: 3800.0000
CPU min MHz: 800.0000
BogoMIPS: 6784.07
虛擬: VT-x
L1d 快取: 32K
L1i 快取: 32K
L2 快取: 256K
L3 快取: 8192K
幻滅是成長的開始 jserv
之前對於這類東西一直覺得很混淆,學OS的時候也只是考過就忘,這次先嘗試把背景知識補齊再開始動工
這部份一直很弱ˊ~ˋ ChenYi
閱讀冼鏡光的並行計算投影片與Toward concurrency的一些重點紀錄
可用 GraphViz 製圖,HackMD 有支援。中文示範: Graphviz - 用指令來畫關係圖吧! jserv
Synchronous
Asynchronous
Conditional Variable
Semaphore(信號標)
在The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software一文中指出由於硬體製作商,在時脈,散熱,與功耗上遇到了瓶頸(提高時脈,提高溫度,但散熱能力仍無法有效改善)又因為64bits系統所用的程式基本型態例如pointer size從 4 -> 8 bytes,而導致cache裡能存的資料更少,所以逐漸轉向multi-processor的製作,及加大CPU內部的cache size。撰寫Concurrency與Parallel的程式變成了一種趨勢,而不是單純地靠著硬體設備上的更新,就能使程式跑的更快更有效率。
不要打錯字,是 "functional (programming) language",知道重要性後,趕快把 recursion 學好,頭腦體操可以磨練你。 jserv
謝謝提醒 chenyi
於 Toward Concurrency 裡有比較詳細的實作,裏面使用的方法是mutex lock,但mutex的獲取與釋放,對於效能會有一定的影響
所以後面又提出了lock-free的解決方案
問題不在於 branch,而在於同步的成本,你之前參照的文章就提到了。 jserv
喔喔 感謝提醒! chenyi
不同於一般的作法,lock-free的作法是給予每個worker threads自己的work queue。
對於main-thread放工作和worker thread取工作的衝突採ring-buffer解決
此實作參考 https://github.com/xhjcehust/LFTPool
在這個實作裡的任務排程為
另外還有做Task migration
資料來自於大家所熟知的 POSIX Threads Programming上
看了一下這邊好像沒什麼能寫的…上面的教學網站太好用了,man上也幾乎都能解決,暫時不做這邊的筆記chenyi
Mutexes
試著從他的mutex範例學習(只紀錄重點)
(程式碼的部份連上去比較好看清楚mutex相關的部份)
pthread_mutex_lock (&mutexsum);
dotstr.sum += mysum;
pthread_mutex_unlock (&mutexsum);
(mutex作用為提供各個執行緒在寫入共享記憶體區段的時候,避免覆(誤)寫的情況出現)
所以在進critical section的時候,要使用pthread_mutex_lock()將寫入鎖住,讓其他執行緒,等待該執行緒寫入完成。
記一下要做什麼
修正一下main.c的部份
entry *etmp;
pHead = pHead->pNext;
for (int i = 0; i < THREAD_NUM; i++) {
if (i == 0) {
pHead = app[i]->pHead->pNext;
dprintf("Connect %d head string %s %p\n", i,
app[i]->pHead->pNext->lastName, app[i]->ptr);
} else {
etmp->pNext = app[i]->pHead->pNext;
dprintf("Connect %d head string %s %p\n", i,
app[i]->pHead->pNext->lastName, app[i]->ptr);
}
etmp = app[i]->pLast;
dprintf("Connect %d tail string %s %p\n", i,
app[i]->pLast->lastName, app[i]->ptr);
dprintf("round %d\n", i);
}
這邊的pHead = pHead->next的部份是沒意義的(pHead->next在此時為NULL)
然後進去迴圈時的i=0的時候,可以省去做判斷,提出來在外面,大部份for迴圈裏面的都是debug用的樣子,另外沒理解錯的話,i = 0的時候phead的值應該要是 app[0]->pHead 才對,etmp目的應該是為了串接分開來的app,所以回圈裡的etmp->pNext應該要給予的值是app[i]->pHead
刪減過後
entry *etmp;
pHead = app[0]->pHead;
etmp = app[0]->pLast;
for (int i = 1; i < THREAD_NUM; i++) {
etmp->pNext = app[i]->pHead;
etmp = app[i]->pLast;
}
重整後在試跑一次確認程式正確性
./phonebook_opt
size of entry : 24 bytes
execution time of append() : 0.005126 sec
execution time of findName() : 0.005588 sec
修正後各thread執行時間
我認為會慢因為原本的方法裏面採的有順序的join (0-1-2-3)
for (int i = 0; i < THREAD_NUM; i++)
pthread_join(tid[i], NULL);
以上面的4條thread為例,若是2先跑完,他必須等到0和1跑完join完才能join進main thread裡
而當thread數目夠高,各thread完成的順序就不會是0-1-2-3-4…而會是看系統怎麼安排(或者原本有寫thread pool去作管控)
先試試看吳彥寬的實驗筆記裡提到的用非同步的join
觀察一下程式碼,可以發現是在各自thread裡做append的動作,最後才做串接,也就是說,整個append的過程應該是沒有critical section的,如果能各自先結束並儲存,不要等待其他thread完成的話,應該能節省不少時間 `chenyi`