Try   HackMD

2017q3 Homework1 (phonebook)

contributed by <yusihlin>

Reviewed by zhanyangch

  • Add files via upload 此種 commit message 不必要,因為 git 可以追蹤檔案的修改,應該寫修改的原因,以及修改後有何不同。
  • 試著回答作業要求裡面的問題

開發環境

$ lscpu
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
型號:              78
Model name:            Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
製程:              3
CPU MHz:             799.072
CPU max MHz:           2800.0000
CPU min MHz:           400.0000
BogoMIPS:              4800.00
虛擬:              VT-x
L1d 快取:          32K
L1i 快取:          32K
L2 快取:           256K
L3 快取:           3072K
NUMA node0 CPU(s):     0-3

安裝 Perf

$ sudo apt-get install linux-tools-common
  • 輸入 perf list 後缺少一些東西
$ uname -r
4.10.0-35-generic
$ sudo apt-get install linux-tools-4.10.0-35-generic linux-cloud-tools-4.10.0-35-generic
  • 輸入 perf top 顯示錯誤畫面,察看權限後修改並取消 kernel pointer 的禁用
$ cat /proc/sys/kernel/perf_event_paranoid
3
$ sudo sh -c "echo -1 > /proc/sys/kernel/perf_event_paranoid"
$ sudo sh -c "echo 0 > /proc/sys/kernel/kptr_restrict"

fork phonebook

  • 先從 phonebook fork 到自己的 repository 後 clone ,編譯並察看輸出
$ git clone https://github.com/yusihlin/phonebook
$ cd phonebook
$ make
$ make run
size of entry : 136 bytes
execution time of append() : 0.074943 sec
execution time of findName() : 0.005417 sec
  • 使用 gnuplot 製圖並察看
$ sudo apt-get install gnuplot //安裝軟體
$ make plot
$ eog runtime.png

  • 使用 perf stat 分析 cache miss (Makefile 中 cache-test 有執行此指令)
$ perf stat --repeat 100 \
                -e cache-misses,cache-references,instructions,cycles \
                ./phonebook_orig

Performance counter stats for './phonebook_orig' (100 runs):

         4,752,110      cache-misses              #   93.834 % of all cache refs      ( +-  0.06% )
         5,064,396      cache-references                                              ( +-  0.16% )
       261,801,297      instructions              #    1.46  insn per cycle           ( +-  0.02% )
       179,657,037      cycles                                                        ( +-  0.23% )

       0.068653651 seconds time elapsed                                          ( +-  1.38% )

可以看到 cache miss 達到 93.8%

  • 使用 perf record & perf report 做熱點分析,針對函式級別進行 event 統計
$ perf record ./phonebook_orig
$ perf report

Samples: 375  of event 'cycles', Event count (approx.): 176128125
Overhead  Command         Shared Object      Symbol  
  14.18%  phonebook_orig  phonebook_orig     [.] main                     
  13.23%  phonebook_orig  phonebook_orig     [.] findName                 
  12.77%  phonebook_orig  libc-2.23.so       [.] __strcasecmp_l_avx       
   8.35%  phonebook_orig  libc-2.23.so       [.] _IO_fgets               
   7.26%  phonebook_orig  libc-2.23.so       [.] _int_malloc             
   5.53%  phonebook_orig  libc-2.23.so       [.] __memcpy_sse2           
   3.78%  phonebook_orig  libc-2.23.so       [.] _IO_getline_info         
   3.73%  phonebook_orig  [kernel.kallsyms]  [k] page_fault               
   3.20%  phonebook_orig  libc-2.23.so       [.] __strcpy_sse2_unaligned 
   3.00%  phonebook_orig  libc-2.23.so       [.] malloc                   
   2.92%  phonebook_orig  [kernel.kallsyms]  [k] clear_page_c_e           
   2.32%  phonebook_orig  [kernel.kallsyms]  [k] free_pcppages_bulk       
   2.19%  phonebook_orig  [kernel.kallsyms]  [k] _raw_spin_lock           
   1.71%  phonebook_orig  phonebook_orig     [.] append                   
   1.52%  phonebook_orig  [kernel.kallsyms]  [k] __pagevec_lru_add_fn     
   1.44%  phonebook_orig  libc-2.23.so       [.] memchr                   
   1.31%  phonebook_orig  [kernel.kallsyms]  [k] __rmqueue               
   0.94%  phonebook_orig  [kernel.kallsyms]  [k] handle_mm_fault         
   0.77%  phonebook_orig  libc-2.23.so       [.] __strcasecmp_avx         
   0.76%  phonebook_orig  [kernel.kallsyms]  [k] __mod_node_page_state   
   0.76%  phonebook_orig  [kernel.kallsyms]  [k] release_pages   

可以看出在 findName 和 strcasecmp_l_avx 的 overhead 較高,可以修改函式的內容以提高效能

優化方法

縮小 Struct

  • 在 main, findname 中只用到了 lastname,但原本的 entry 除了 lastname 還包括其他資料,大小為 136 Bytes,而 L1 cache 大小 32KB,如果能縮減 entry 的大小使 L1 cache 一次能儲存的數量增加,應能降低 cache miss 的機率。
  • 將原本 entry 的其他資料移至 detail 儲存
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;

typedef struct __PHONE_BOOK_ENTRY {
    char lastName[MAX_LAST_NAME_SIZE];
    detail *data;
    struct __PHONE_BOOK_ENTRY *pNext;
} entry;
  • 更改後 struct entry 的大小剩下 32 Bytes
size of entry : 32 bytes
execution time of append() : 0.070914 sec
execution time of findName() : 0.002730 sec
  • cache miss 從 93.8% 降至 70%
Performance counter stats for './phonebook_opt' (100 runs):

         1,733,721      cache-misses              #   70.045 % of all cache refs      ( +-  0.19% )
         2,475,146      cache-references                                              ( +-  0.61% )
       244,622,619      instructions              #    1.86  insn per cycle           ( +-  0.02% )
       131,384,158      cycles                                                        ( +-  0.67% )

       0.050672494 seconds time elapsed                                          ( +-  1.72% )
  • 時間比較圖

使用 hash function

  • 原本的資料結構使用 linked list,每次搜尋均需從頭開始,當資料筆數龐大時效益不高,樣本數 35 萬筆,透過 hash function 產生新的雜湊值使得資料量變小,當產生兩個相同的雜湊值時會發生 collision,解決方法採用 linked list 繼續比對資料
  • 參考 各种字符串Hash函数比较 使用 BKDRHash
unsigned int BKDRHash(char *str)
{
    unsigned int seed = 131; // 31 131 1313 13131 131313 etc..
    unsigned int hash = 0;

    while (*str)
    {
        hash = hash * seed + (*str++);
    }

    return (hash & 0x7FFFFFFF);
}
  • cache miss 下降到 41.6%
Performance counter stats for './phonebook_hash' (100 runs):

         1,071,269      cache-misses              #   41.564 % of all cache refs      ( +-  0.70% )
         2,577,372      cache-references                                              ( +-  0.53% )
       244,040,130      instructions              #    1.51  insn per cycle           ( +-  0.02% )
       161,480,391      cycles                                                        ( +-  0.30% )

       0.061633582 seconds time elapsed                                          ( +-  1.78% )

  • 時間比較圖
    ![](
    findname 的時間大幅降低,但 append 的時間增加,因為多了建立 hash table 的時間

參考文獻