<style> h2.part{color:#000000;} h3.part{color:#D92424;} h4.part{color:#005BB0;} h5.part{color:#FD6F0A;} h6.part{color:#4400B0;} </style> # 2017q1 Homework4的觀念 (phonebook-concurrent) ## 開發環境 ```shell Architecture: x86_64 CPU 作業模式: 32-bit, 64-bit Byte Order: Little Endian CPU(s): 4 On-line CPU(s) list: 0-3 每核心執行緒數:2 每通訊端核心數:2 Socket(s): 1 NUMA 節點: 1 供應商識別號: GenuineIntel CPU 家族: 6 型號: 42 Model name: Intel(R) Core(TM) i5-2410M CPU @ 2.30GHz 製程: 7 CPU MHz: 799.890 CPU max MHz: 2900.0000 CPU min MHz: 800.0000 BogoMIPS: 4589.59 虛擬: VT-x L1d 快取: 32K L1i 快取: 32K L2 快取: 256K L3 快取: 3072K NUMA node0 CPU(s): 0-3 ``` ## [Toward Concurrency](https://hackmd.io/s/Skh_AaVix) * concurrency(並行) 把可以拆開的部份拆開,不一定要同時執行 ![](https://i.imgur.com/rweOyiD.png) * parallelism(平行) 把程式拆開"一起執行",最後在一起相加 ![](https://i.imgur.com/Oom3wM5.png) multithread環境下,程式會出問題,往往在於執行順序的不確定性。 * Concurrency * Sequenced-before * 一種對同一個 thread 下,求值順序關係的描述 * Happens-Before * 前一個操作的效果在後一個操作執行之前必須要可見 * Synchoronizes-with * 跨 thread 版本的 happens-before * Sequential Consistency 1. 對於每個獨立的處理單元,執行時都維持程式的順序 (Program Order) 2. 整個程式以某種順序在所有處理器上執行 ## 開發紀錄 首先是最原始的效能 ![](https://i.imgur.com/OVpgE7x.png) 先研究一下 opt 的版本,一開始看到有的是 `ifndef OPT` 有的是 `if defined(OPT)` 蠻不習慣的,有時間把它改好看一點。 還沒使用過 pthread 要先讀個 code > 有個疑惑,不知道為什麼`pthread_setconcurrency(THREAD_NUM + 1);`要+1 看完沒什麼好改善的主意,只好來選讀的前一屆的共筆,這邊我想到我在 Homework1 就有觀察過 memory leak 了 ## memory leak 先使用以前用過的 `valgrind` 來觀察一下,能看到有 4 個尚未被 free,opt 版本裡面跟 4 有關的應該就是 pthread_num ,我大概是各個 thread 裡面有東西忘記 free ``` $ valgrind --leak-check=full ./phonebook_opt ==10606== HEAP SUMMARY: ==10606== in use at exit: 1,614 bytes in 4 blocks ==10606== total heap usage: 24 allocs, 20 frees, 8,421,863 bytes allocated ==10606== ==10606== LEAK SUMMARY: ==10606== definitely lost: 0 bytes in 0 blocks ==10606== indirectly lost: 0 bytes in 0 blocks ==10606== possibly lost: 0 bytes in 0 blocks ==10606== still reachable: 1,614 bytes in 4 blocks ==10606== suppressed: 0 bytes in 0 blocks ``` 參考[LanKuDot (李昆憶)](https://hackmd.io/s/HJFhaiAp#asanaddres-sanitizer),這裡面提到 asan 這個東西,在 Makefile 裡面也有放上相關的參數 ``` ifdef CHECK_LEAK CFLAGS_common += -fsanitize=address -fno-omit-frame-pointer endif ``` 大概是誘惑我去用用看 ``` $ make CHECK_LEAK=1 $ ./phonebook_opt orginal file size = 3206080 size of entry : 24 bytes execution time of append() : 0.010399 sec execution time of findName() : 0.007910 sec ``` !!!我還以為哪個指令下錯了,結果再去共筆看一下,原來我發現剩餘的 memory leak 也是屬於 `still reachable` 這邊因為還有有效指標指著未被 free 的記憶體區塊,並不會對程式造成什麼影響,所以 asan 才會沒有顯示出錯誤訊息 https://hackmd.io/s/BkN1JNQp alligment!!?why?? 對齊是為了 entry pool 每段名字一樣長可以方便 但是由於我們不知道每個字元的 offset,所以我先將 file整理成 16 char alignment,之後再使用 mmap(),就能夠直接 offset 16到下一個 word了。 ### mmap * original原本一直用append fget去拿資料,這樣太慢 * 因此我們用mmap比較快 * file.txt => 把它對應到memory可以節省到i/o處理的資源 * mmap 用法 http://man7.org/linux/man-pages/man2/mmap.2.html ```clike= #include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void *addr, size_t length); ``` size_t => unsigned int offset :pointer面對string指向下一比資料的起始位置 * phonebook concurrent mmap 修改的地方 ```clike= char *map;//void pointer 需要拿來提取memory裡面的資料 map = mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); ``` [memory pool](https://hackmd.io/KwMwLGDGBMAMCcBaaA2AzADkWAJgRgENF4B2fYlOE2A+HAI0jCA=?both) ### aligment ```clike= ``` 在main.c裡面會用到!!! 因為經過alligment後每筆資料大小現在是一樣的,故可以用固定的offset來存取資料 ### main.c ```clike= text_align(DICT_FILE, ALIGN_FILE, MAX_LAST_NAME_SIZE); //讀完原始檔後 由text_align這個function每筆資料寫成同樣大小 //Max_Last_Name (每筆資料寫成16個byte的大小) //if define 寫在h檔裡面 ``` ```clike= /* build the entry"*/ entry *pHead, *e; // entry就是linkedlist的結構 // 宣告 e, pHead兩個指向 entry 結構的指標 /* typedef struct __PHONE_BOOK_ENTRY { char *lastName; struct __PHONE_BOOK_ENTRY *pNext; pdetail dtl; } entry; */ ``` ```clike= map = mmap(NULL, file_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); /* * 我們把align(對齊的file每行固定大小)對應到memory裡面 * 因為是記憶體,所以要已指標的形式來宣告 map * */ ``` ```clike= entry_pool = (entry *)malloc(sizeof(entry) * file_size / MAX_LAST_NAME_SIZE); ``` 這一段就是memory pool的部份先malloc要的記憶體 省時間 ```clike= for (int i = 0; i < THREAD_NUM; i++) // Created by malloc, remeber to free them. thread_args[i] = createThread_arg(map + MAX_LAST_NAME_SIZE * i, map + file_size, i, THREAD_NUM, entry_pool + i); ``` 因為我們要對append(寫好的function)做平行處理 所以我們來設定參數 ```clike= for (int i = 0; i < THREAD_NUM; i++) pthread_create(&threads[i], NULL, (void *)&append, (void *)thread_args[i]); ``` 對append開始做平行處理!!! ```clike= for (int i = 0; i < THREAD_NUM; i++) pthread_join(threads[i], NULL); ``` 因為thread做完,把thread給(destory)關掉 ```clike= /* Connect the linked list of each thread */ for (int i = 0; i < THREAD_NUM; i++) { if (i == 0) { pHead = thread_args[i]->lEntry_head->pNext; DEBUG_LOG("Connect %d head string %s %p\n", i, pHead->lastName, thread_args[i]->data_begin); } else { e->pNext = thread_args[i]->lEntry_head->pNext; DEBUG_LOG("Connect %d head string %s %p\n", i, e->pNext->lastName, thread_args[i]->data_begin); } e = thread_args[i]->lEntry_tail; DEBUG_LOG("Connect %d tail string %s %p\n", i, e->lastName, thread_args[i]->data_begin); DEBUG_LOG("round %d\n", i); } ``` example: 假設現在有1,2,3,4得資料 thraed1拿資料1 thread2拿資料2 thread3拿資料3 thread4拿資料4 所以要拿出資料需要將所有的資料儲存在一起 ```clike= /* Find the given entry */ /* the givn last name to find */ char input[MAX_LAST_NAME_SIZE] = "zyxel"; e = pHead; ``` 你要找字串的名子 ```clike= /* Release memory */ #ifndef OPT while (pHead) { e = pHead; pHead = pHead->pNext; free(e); } ``` 未優話的部份 ```clike= #else /* Release memory */ /* Free the allocated detail entry */ e = pHead; while (e) { free(e->dtl); e = e->pNext; } free(entry_pool); for (int i = 0; i < THREAD_NUM; ++i) free(thread_args[i]); munmap(map, file_size); close(fd); ```