sysprog2017
dev_record
contributed by <HTYISABUG
>
TODO: 列出 malloc 後得到的位置
$ uname -a
Linux overworld 4.10.0-35-generic
#39-Ubuntu SMP Wed Sep 13 09:02:42 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 94
Model name: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
Stepping: 3
CPU MHz: 1137.817
CPU max MHz: 3500.0000
CPU min MHz: 800.0000
BogoMIPS: 5184.00
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 6144K
NUMA node0 CPU(s): 0-7
$ free
total used free shared buff/cache available
Mem: 16326616 2833948 10524780 82344 2967888 13008632
Swap: 2097148 0 2097148
這份作業的目標是降低執行時的 cache miss 與時間
先上網查詢降低 cache miss 的方法
查閱幾篇在 stackoverflow 與 wikipedia 的文章後
為何先不看經典著作呢?你需要完整的背景知識
"jserv"
統整出降低 cache miss 的方法大概有
條列下來幾乎都是要求資料連續
但現在不知道電話簿內會有幾份資料
因此採用陣列讓資料連續不可行
先檢查 phonebook_orig.c
可以從 append 觀察到原版採用的資料結構是 linked list
linked list 只能一條線查到底,中途有很多不必要的資料也被查到
將資料依規則建表方式重新儲存應該能減少查詢時間
因此決定採用建立 hash table 的方法
是 linked list,有 "-ed"
"jserv"
以不同的 hash function 及 hash table 長度當作變數進行實驗
Hash function 用單純取 mod 及 BKDR 兩種, hash table 長度 2017
實驗結果如下
hash function | cache-misses | time(sec) |
---|---|---|
original | 90.158% | 0.055980279 |
mod | 69.753% | 0.102894758 |
bkdr | 68.086% | 0.109897377 |
Hash table 長度 2017
, 4013
, 8191
, hash function 用 bkdr
實驗結果如下
hash function | cache-misses | time(sec) |
---|---|---|
original | 90.158% | 0.055980279 |
bkdr 2k | 68.086% | 0.109897377 |
bkdr 4k | 69.324% | 0.111428125 |
bkdr 8k | 69.412% | 0.111067196 |
從以上結果看, 建立 hash table 可以有效降低 findName 的執行時間
cache-miss 也略降低至原本的 0.75~0.77 倍
append 時間提升至 1.22-1.25 倍
總時間升至原本的 2 倍
從實驗結果看來 hash table 能再縮短搜索時間起到極佳的作用
且能略為減少 cache-miss
使用精確的話語來描述,像是 hash function 的選擇和資料表示的策略
"jserv"
要減少 cache miss 的話還能讓 cahce 塞下儘可能多筆資料
#define MAX_LAST_NAME_SIZE 16
typedef struct __PHONE_BOOK_ENTRY {
char lastName[MAX_LAST_NAME_SIZE]; // 16B
char firstName[16]; // 16B
char email[16]; // 16B
char phone[10]; // 10B
char cell[10]; // 10B
char addr1[16]; // 16B
char addr2[16]; // 16B
char city[16]; // 16B
char state[2]; // 2 B
char zip[5]; // 5 B
struct __PHONE_BOOK_ENTRY *pNext; // 8 B
} entry; // 131B
這是電腦的 cache 資訊
L1d cache: 32K
L2 cache: 256K
L3 cache: 6144K
現在大約有 350k 筆資料,總合約 46mB
就算三層 cache 合計也不過能存 50k 左右筆資料
當然 cache-miss 在 findName 時會噴的很嚴重
當前 append 跟 findName 在使用時都只用到 lastName 跟 pNext
所以其他的資料欄位其實可以到需要用時再分配空間
光是這樣就預計每筆資料能省下 99B , 總計 35mB 的空間
資料結構更改如下
typedef struct {
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];
} Data;
typedef struct __PHONE_BOOK_ENTRY {
char lastName[MAX_LAST_NAME_SIZE]; // 16B
Data *data; // 8 B
struct __PHONE_BOOK_ENTRY *pNext; // 8 B
} entry; // 32B
Hash function 選用 bkdr , table size 2017
實驗結果如下
hash function | cache-misses | time(sec) |
---|---|---|
original | 90.248% | 0.056481064 |
bkdr 2k | 68.086% | 0.109897377 |
optimized | 64.235% | 0.067265891 |
縮小資料讓 append 比沒縮小的情況快了一些
同時也降低了 cache miss
實驗中出了個小問題,讀取到的資料大小跟預期的不符
資料大小 | 預計 | 實際 |
---|---|---|
original | 131B | 136B |
經同學提醒才想起還有 data structure alignment 這回事
系統架構為 64bits , 所以一次應該會捉 8B
不過這不影響實驗結果,只是個插曲
看了 HMKRL 的共筆才發現之前在做 hash table 測試的時候資料都沒更新
修改 cache-test 讓資料能更新
上方結果圖已更新
在作業公佈欄底下有看到 memory pool
在 wikipedia 查了一下就做了個簡單的版本試試效果
typedef struct {
void *ptr;
size_t size;
size_t cnt;
} MemPool;
void *poolloc(size_t size, MemPool *pool)
{
if (pool->cnt + size <= pool->size) {
void *p = pool->ptr + pool->cnt;
pool->cnt += size;
return p;
}
return NULL;
}
Memory pool 先給了 400k 筆資料的空間
實驗結果如下
hash function | cache-misses | time(sec) |
---|---|---|
original | 90.248% | 0.056481064 |
bkdr 2k | 68.086% | 0.109897377 |
bkdr + reduce | 64.235% | 0.067265891 |
bkdr + reduce + pool | 40.694% | 0.028073860 |
實驗結果顯示 memory pool 大幅減少 append 的時間
應該是因為 malloc 所耗去的時間藉由預先分配一大段記憶體 (memory pool)來省掉了
同時因為 pool 是一整段連續記憶體
所以讀取時 cache miss 也比前一版本更進一步降低
本例選用的 dataset (也就是輸入電話簿的姓名) 是否存在問題?
是,大部份甚至都不是名字
有代表性嗎?跟真實英文姓氏差異大嗎?
無代表性
雖然有意義的單字也不少
但連zzzzzzz
這種毫無意義的東西都混進來了
現實根本沒人用這種姓氏
資料難道「數大就是美」嗎?
要視情況
例如在人工智慧、資料分析領域大量的資料就像寶藏
但其他領域大量的資料可能讀取困難
導致拖垮效能
如果我們要建立符合真實電話簿情境的輸入,該怎麼作?
How does one write code that best utilizes the CPU cache to improve performance? (stackoverflow)