contributed by <ryanwang522
>
Reviewed by csielee
初始設定並初步執行
清空 cache
$ echo 1 | sudo tee /proc/sys/vm/drop_caches
$ sudo make cache-test
size of entry : 136 bytes
execution time of append() : 0.042719 sec
execution time of findName() : 0.006162 sec
Performance counter stats for './phonebook_orig' (100 runs):
123,6017 cache-misses # 85.500 % of all cache refs ( +- 0.29% )
144,5636 cache-references ( +- 0.19% )
2,6300,2705 instructions # 1.25 insn per cycle ( +- 0.02% )
2,1018,3982 cycles ( +- 0.17% )
0.064090511 seconds time elapsed ( +- 0.19% )
看到報表後覺得怪怪的,miss rate 和其他人好像有些差異Ryan Wang
$ sudo make plot
main.c
的程式碼,不管是append()
或findName()
在原本龐大的 struct 裡面只有用到 lastName
,縮小結構內容應該對於效能改善助益不少。malloc()
給 detail 所指向的內容,往後需要用到內容時再分配空間給它就好。
typedef struct __PHONE_BOOK_DETAIL_ENTRY {
char firstName[16];
char email[16];
char phone[10];
char cell[10];
char addr1[16];
char addr2[16];
char city[16];
char state[2];
char zip[5];
} detailEntry;
typedef struct __PHONE_BOOK_ENTRY {
char lastName[MAX_LAST_NAME_SIZE];
detailEntry *detail;
struct __PHONE_BOOK_ENTRY *pNext;
} entry;
$ sudo perf stat --repeat 100 -e cache-misses,cache-references,instructions,cycles ./phonebook_opt
size of entry : 32 bytes
execution time of append() : 0.032987 sec
execution time of findName() : 0.002091 sec
Performance counter stats for './phonebook_opt' (100 runs):
16,1809 cache-misses # 40.110 % of all cache refs ( +- 0.73% )
40,3410 cache-references ( +- 0.81% )
2,4494,4046 instructions # 1.78 insn per cycle ( +- 0.02% )
1,3755,3543 cycles ( +- 0.51% )
0.042066988 seconds time elapsed ( +- 0.57% )
$ sudo make plot
記得以前在上 OS 課程時有學過 hash 的基本概念,主要是由建立適當大小的 hash table 後,接著再由雜湊函數得到的雜湊值對照 table 去增加查找效率。而需要注意的地方有以下幾點。
unsigned int BKDRHash(char *str)
{
unsigned int seed = 31;
unsigned int hash = 0;
while(*str)
hash = hash * seed + (*str++);
return (hash % MAX_TABLE_SIZE);
}
之所以會將原來的雜湊值對 table size 取餘數是因為 size 大小的限制,而當 Collision(碰撞)發生,我採取的方法是 Chaining,直接將重複的資料串在一起,示意圖如下。
main.c
做了相對應的修改,原本的 singly linked list 就保留著沒使用。
while (fgets(line, sizeof(line), fp)) {
while (line[i] != '\0')
i++;
line[i - 1] = '\0';
i = 0;
unsigned int hashVal = BKDRHash(line);
if (hashTable[hashVal] == NULL) {
hashTable[hashVal] = (entry *) malloc(sizeof(entry));
tableHead[hashVal] = hashTable[hashVal];
}
hashTable[hashVal] = append(line, hashTable[hashVal]);
}
...
/* the givn last name to find */
char input[MAX_LAST_NAME_SIZE] = "zyxel";
unsigned int hashVal = BKDRHash(input);
e = tableHead[hashVal];
assert(findName(input, e) &&
"Did you implement findName() in " IMPL "?");
assert(0 == strcmp(findName(input, e)->lastName, "zyxel"));
發現好像直接修改
main.c
在執行make
時會因為 #ifdef 的問題導致有原始版本的編譯錯誤,不過就先測試看看,以後再來修改Ryan Wang
$ make phonebook_opt
$ sudo perf stat --repeat 100 -e cache-misses,cache-references,instructions,cycles ./phonebook_opt
size of entry : 32 bytes
execution time of append() : 0.042810 sec
execution time of findName() : 0.000003 sec
Performance counter stats for './phonebook_opt' (100 runs):
11,1048 cache-misses # 26.915 % of all cache refs ( +- 3.29% )
41,2592 cache-references ( +- 0.33% )
2,3471,2581 instructions # 1.56 insn per cycle ( +- 0.02% )
1,5043,4217 cycles ( +- 0.58% )
0.046055866 seconds time elapsed ( +- 0.60% )
發現 append()
的執行時間有些許的增加
findName()
的時間大幅下降,證明了使用 hash function 雖然會犧牲一點append()
的效能,但著實能改善效率。Cache misses 從 40% 降為 26.91% ,因為適當的 table size 使得不僅查找效率提昇,miss rate 也順帶降低。
參考 Conditional Compilation,解決先前的 orig 版本編譯問題。
append()
以及findName()
進行修改的,直接改main.c
似乎有點不明智QQ$ sudo make plot
output.txt
還是舊的,執行$ make clean
後就可以正常使用 plot
了。因為先前 commit 的內容被 jserv 教授糾正了不少錯誤的寫法,所以花了一點時間研究 How to write a git commit message,這裡稍微整理一下。
Commit message 大致分為:
Subject line
Message body
最後看到一句不錯的話紀錄一下
strcasecmp()
的回傳值作為要放置在左子樹還是右子樹的依據。
strcasecmp()
The strcasecmp() function performs a byte-by-byte comparison of the
strings s1 and s2, ignoring the case of the characters. It returns
an integer less than, equal to, or greater than zero if s1 is found,
respectively, to be less than, to match, or be greater than s2.
這裡還是不太懂 byte-by-byte comparsion 的實際運作方法,回頭再來研究。 Ryan Wang
int Length(entry *head)
{
int count = 0;
entry *current = head;
while (current != NULL) {
count++;
current = current -> pNext;
}
return count;
}
treeNode *BuildBST(entry **headRef, int length)
{
if(length <= 0)
return NULL;
treeNode *left = BuildBST(headRef, length/2);
treeNode *root = (treeNode *) malloc(sizeof(treeNode));
root->entry = *headRef;
root->left = left;
*headRef = (*headRef)->pNext;
root->right = BuildBST(headRef, length - (length / 2 + 1));
return root;
}
findName()
,利用循序的方式找出目標。
entry *findName(char lastName[], treeNode *root)
{
treeNode *current = root;
int result;
while (current != NULL && (result = strcasecmp(lastName, current->entry->lastName)) != 0) {
if (result < 0)
current = current -> left;
else
current = current -> right;
}
if (!root)
return (current->entry);
else
return NULL;
}
./phonebook_bst
size of entry : 32 bytes
execution time of append() : 0.037696 sec
execution time of findName() : 0.000000 sec
Performance counter stats for './phonebook_bst' (100 runs):
20,8875 cache-misses # 53.635 % of all cache refs ( +- 0.70% )
38,9440 cache-references ( +- 0.82% )
2,8554,9675 instructions # 1.57 insn per cycle ( +- 0.02% )
1,8135,1595 cycles ( +- 0.67% )
0.055963158 seconds time elapsed ( +- 0.80% )
可見 findName()
的時間又比 hash function 版本有些微的減少。
runtime.gp
firstname
+ lastName
,而本例只有 firstname
。