contributed by < etc276
>
etc276
yanang
entry *pHead = (entry *) malloc (sizeof(entry));
pHead->pNext = e[hash];
e[hash] = pHead;
strcpy(pHead->lastName,lastName);
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 2
Core(s) per socket: 2
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 61
Model name: Intel(R) Core(TM) i7-5500U CPU @ 2.40GHz
Stepping: 4
CPU MHz: 2400.878
CPU max MHz: 3000.0000
CPU min MHz: 500.0000
BogoMIPS: 4788.90
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 4096K
NUMA node0 CPU(s): 0-3
typedef struct __PHONE_BOOK_ENTRY {
char lastName[MAX_LAST_NAME_SIZE];
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];
struct __PHONE_BOOK_ENTRY *pNext;
} entry;
$ git clone https://github.com/etc276
$ cd phonebook
Performance counter stats for './phonebook_orig' (100 runs):
3,430,005 cache-misses # 81.729 % of all cache refs ( +- 0.03% )
4,196,826 cache-references ( +- 0.19% )
261,712,845 instructions # 1.34 insn per cycle ( +- 0.02% )
194,998,572 cycles ( +- 0.26% )
0.066929775 seconds time elapsed ( +- 0.31% )
typedef struct __PHONE_BOOK_ENTRY {
char lastName[MAX_LAST_NAME_SIZE];
struct __PHONE_BOOK_ENTRY *pNext;
struct __PHONE_BOOK_DETAIL *pDetail;
} entry;
typedef struct __PHONE_BOOK_DETAIL {
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];
} detail;
目前我對 cache miss 對效能影響的理解為,由於記憶體使用上並不連續,造成 latency 變長。所以先從 struct 下手,讓會用到的資料盡量連續,而方法為將 lastname 以外的資料存在別的地方,減少單一 entry 的大小。
如下方所示,cache miss下降到 69.64%,執行時間也稍有下降。
Performance counter stats for './phonebook_opt' (100 runs):
1,210,125 cache-misses # 69.643 % of all cache refs ( +- 0.19% )
1,737,624 cache-references ( +- 0.58% )
243,060,991 instructions # 1.75 insn per cycle ( +- 0.02% )
139,089,184 cycles ( +- 0.78% )
0.048515702 seconds time elapsed ( +- 0.93% )
entry *findName(char lastName[], entry *e[])
{
int hash = BKDRHash(lastName)%TABLE_SIZE;
entry *tmp = e[hash];
while (tmp) {
if (!strcasecmp(lastName, tmp->lastName))
return tmp;
tmp = tmp->pNext;
}
return NULL;
}
void append(char lastName[], entry *e[])
{
int hash = BKDRHash(lastName)%TABLE_SIZE;
entry *tmp = e[hash];
while (tmp->pNext) {
tmp = tmp->pNext;
}
entry *node = (entry *) malloc(sizeof(entry));
tmp->pNext = node;
strcpy(node->lastName, lastName);
}
減少 cache miss 之後,下一步想辦法從演算法減少時間。 original 的版本就是從頭到尾跑一遍查詢,這在資料量龐大時效能會有明顯影響,所以就想用 O(1) 的 hash 去實現。而在參考完 Section 8 哈希表(Hash Table),決定使用 BKDR 加上"拉鍊法"處理 hash 和碰撞的問題。
而實際上 coding 才想到自己其實不知道要怎麼選 table size,參考完 为什么hash table的大小最好取一个不接近2^p的素数 還是不知道要選用什麼數量級的質數,再繼續看其他人的開發紀錄,發現有人實驗過,最後決定選用 20k 附近且有在這篇中出現的 21911 作為 table size.
cache miss 下降到 36.93%
Performance counter stats for './phonebook_opt' (100 runs):
15,649 cache-misses # 36.930 % of all cache refs ( +- 1.25% )
42,374 cache-references ( +- 0.61% )
691,447 instructions # 0.59 insn per cycle ( +- 0.20% )
1,169,857 cycles ( +- 1.09% )
0.121802201 seconds time elapsed ( +- 1.69% )
所以你還是沒搞懂,快去「找教科書」(不要茫茫網海亂找) 來讀,我等你的報告 –jserv
有代表性嗎?跟真實英文姓氏差異大嗎?
words.txt 中有大量沒有意義的單字(ex.aaaaa),寫一個比較的程式發現,words.txt 裡總共 349900 筆資料,但是只有 7426 筆資料有在 all-names.txt 裡出現,所以 words.txt 對 phonebook 來說代表性很低。
首字母 單詞頻率
t 16.671%
a 11.602%
s 7.755%
h 7.232%
w 6.753%
i 6.286%
o 6.264%
b 4.702%
m 4.374%
f 3.779%
c 3.511%
l 2.705%
d 2.670%
p 2.545%
n 2.365%
e 2.007%
g 1.950%
r 1.653%
y 1.620%
u 1.487%
v 0.649%
j 0.597%
k 0.590%
q 0.173%
x 0.037%
z 0.034%
目前電腦的架構為處理器(CPU)+記憶體(RAM),但由於近 40 年來兩者的速度差異越來越大,所以就利用 cache 去減少效能上的差異。
cache 是 CPU 外層的記憶體,其容量相當小,藉由將 RAM 的一部分資料先放在上面,減少兩者溝通的時間。而因為 cache 的容量遠小於 RAM,所以如果 CPU 要用到的下一個資料不在 cache 上,就會發生 cahe miss,反之則為 cache hit。
cache 的原理為:「實務上會使用到的記憶體常為連續性的」。所以才有辦法藉由這種結構去彌補兩者之間的速度差(可以先把資料放上 cache ),因此第一個優化方法就是先盡量將資料連續化。
perf 是一種效能分析工具。他會幫你計算 cache miss, instructions 等等,去分析程式的效能瓶頸。相關資料請參閱 perf 原理和實務
> set title 'my plot' @ 設定圖片名稱
> set xlabel 'x axis' @ 設定XY軸座標名稱
> set ylabel 'y axis'
> set terminal png @ 設定輸出格式為 .png
> set output 'output_plot.png' @ 設定輸出檔名
> plot [1:10][0:1] sin(x) @ 畫出 sin(x) 函式
@ x軸座標範圍 1-10
@ y軸座標範圍 0-1
使用相同的 coding style 有助於多人開發(commit style也是),而這次的規範為:
$ astyle --style=kr --indent=spaces=4 --indent-switches --suffix=none *.[ch]
--style=kr
用 K&R 的風格進行編程
--indent=spaces=4
四個空格
--indent-switches
關於 Case 的縮排
Indent 'switch' blocks, so that the inner 'case XXX:'
headers are indented in relation to the switch block.
--suffix=none
不保存原始文件
*.[ch]
所有的.c或.h檔
「預計」就不要寫出來,Show me the code!
HackMD 會幫你記錄每筆紀錄的變更和具體時間,把時間省下來發現並解決問題 –jserv
在寫 hash 的時候,產生一堆 bug ,時間詭異,圖也沒有出現。後來才知道是 main.c 錯太多東西,導致像 orig.txt 等文件並沒有產生成功,索性刪掉重新 clone 。之後先仔細閱讀 Makefile 裡各 source code 的相依性,才知道要先從 opt.h 修改起,像是我修改 function 的 prototye,這時 main.c 就需要去利用 #ifdef OPT 做修改,最後才是 opt.c 的小 bug .