contributed by < quantabase13
>
在 dict
目錄下完成編譯後,執行
可以得到 bench_ref.txt
文件。
為了確認這份文件中的資料代表的意思,觀察了test_common.c
中的程式碼,其中一部份如下:
bench_test
的程式碼被定義在 bench.c
,裡面的程式碼如下:
關鍵是中間的 while
迴圈。仔細觀察後可以發現 bench_test
的工作內容就是將 cities.txt
中的城市名稱取出,取其前三個字母作為 prefix (根據 #define PREFIX_LEN 3
),傳進函式 tst_search_prefix
,最後將 tst_search_prefix
執行的時間寫入 bench_ref.txt
。
從 test_common.c
中,可以知道命令
裡的 CPY
決定了 string 在 ternary search tree
的儲存方式。
試著將其視覺化後,得到下圖:
如果改成執行
視覺化後能得到下圖:
在 inputfile 皆為 cities.txt
的條件下,可以發現 CPY
的版本 與 REF
比起來,執行 tst_search_prefix
有明顯速度上的差異。
嘗試用 perf
來細部分析這兩種不同命令,得到如下兩個結果:
奇怪的是,執行
出來的結果中,發現每個 cycle 只執行不到1個 instruction。
為了深入釐清,我們使用 perf record -g
取樣:
接著以 perf report
讀取報告:
得到以下結果:
發現 tst_suggest
花掉最多CPU 週期(self overhead
佔總體的 97.38%
),因此我們決定細部分析 tst_suggest
所用到的指令:
這裡列出的其實就是 function 的 disassembly code
。我們觀察到 tst_suggest(p->lokid, c, nchr, a, n, max);
這個 function call 中,mov 0x8(%rax),%rax
指令所占時間比例最高,於是我們想確認 %rax
對應 function call 中的那一個 argument。
參考相關的第一手資料 x86-64 psABI 3.2.3 [Parameter Passing]
• Arguments of types (signed and unsigned) _Bool , char , short , int , long , long long ,
and pointers are in the INTEGER class.
Passing Once arguments are classified, the registers get assigned (in left-to-right
order) for passing as follows:
- If the class is MEMORY, pass the argument on the stack.
- If the class is INTEGER, the next available register of the sequence %rdi,
%rsi, %rdx, %rcx, %r8 and %r9 is used.
.- If the class is SSE, the next available vector register is used, the registers
are taken in the order from %xmm0 to %xmm7.- If the class is SSEUP, the eightbyte is passed in the next available eightbyte
chunk of the last used vector register.- If the class is X87, X87UP or COMPLEX_X87, it is passed in memory.
我們可以確認,在 tst_suggest(p->lokid, c, nchr, a, n, max);
中:
%rdi
存的是 p->lokid
%esi
存的是 c
%rdx
存的是 nchr
%rcx
存的是 a
%r8
存的是 n
%r9d
存的是 max
並且,p->lokid
這項操作花了最長的時間 。
到這裡似乎可以深入分析是否是 cache misses
造成的影響了,但是我遇到一個不知如何解釋的狀況:
執行命令
接著細部分析 tst_suggest
的指令熱點,會發現:
同樣都使用相同的 event
,只是差別在有沒有在命令列顯式給出使用的 event
(根據Perf wiki , perf record 預設採用 cycles
作為 Sampling event
),最後的熱點竟然不同。
同時,若使用perf top
命令即時監控的話,會發現熱點仍然是 mov 0x8(%rax),%rax
。
另外,若是用 perf
分析 CPY
的版本,也會得到類似的 output:
如果兩種實作都有同樣位置的熱點,是否表示 CPY
的實作方式的其實沒辦法降低 cache misses ?
我們比較看看 cache-misses
event 的數量:
CPY
的版本。得到
REF
的版本。得到
兩者的 cache-misses
的數量比為。
再看看當輸入命令為
或
時,$ perf top -e cache-misses
會給出哪個指令是熱點的判斷。
結果發現熱點是 mov -0x10(%rbp),%r8d
,也就是 mov 0x8(%rax),%rax
的下一條指令。
論文的連結在此
Xor Filters 的演算法可分為四個部份:
以下我依序針對各個部份記錄我的理解。
這部份的演算法很簡短,只有比較 是否與 相等。我們透過這部份的演算法得知 input 是否屬於當初構建 Xor Filter 時所用的 Key Set (就是判斷資料庫裡有沒有 )。
從這部份可以看出演算法的架構,內容如下:
我們需要:
演算法的工作流程:
independent from the fingerprint function
, 但我不懂一個 function indepentdent from
另一個 function 的定義是什麼。需要查閱相關資料。)我們最後能得到:
這一部份出現了兩個新 function 及 ,它們是 Xor Filter 這個演算法的核心,分別對應到 Mapping Step
和 Assigning Step
的想法如下:
接下來:
最後:
如果 stack 大小 ||=|S| ,return success and the stack 。
我們在此先分析 stack 元素的特性:
根據
如果 只有一個元素 ,就把 (, ) push 到 stack
這項規則,每次將 (, ) push 到 stack 後, 會與用之後遇到的每個 計算出的 , , 不同
因此,我們最後得到的 stack 有一個非常重要、之後的 Assigning Step
會用到的特點:
is different from ,, for all keys encountered so far.
這裡的 encounter so far
的時間線是指從 stack 頂端到 stack 底端遍歷的方向。
的 參數 是 stack , B, hash funtions 、、。這一步的工作如下:
對 stack 中的每一個元素(, ):
由於 Stack 中元素的特性:
is different from ,, for all keys encountered so far.
所以每個 都只會被 set 一次。
雖然不妨礙理解演算法,但一個令人疑惑的問題是,Assigning Step
中,,(至少其中兩個) 在還沒被初始化前就被引用,這樣的表達方式是否合理?
xor_singleheader 的實作程式碼在此
xor_singleheader 實作上跟 bloom filter 有一個差別,導致不能直接將其引入程式碼:
相反的,bloom filter 容許重複元素。
因此,決定先對資料進行前處理。前處理的步驟如下:
cities.txt
資料根據 資料檔案的分析 所述加以修改,以利解析。array
,並進行排序。array
中重複的元素移除。array
中每個元素計算其 hash 值(使用 djb2
作為 hash function)以下是我前處理的核心程式碼(第一次嘗試):
我把它稱作第一次嘗試,是因為檢查 hash 出來的結果時,發現有duplicate (共有 1 個)。
把 hash funciton 從 dbj2
換成 jenkins
後就沒有 duplicate 發生。
使用的 jenkins
實作如下:
接下來就是實作 benchmark 的相關程式:
想法是:從 stringSet
裡隨機選擇一個 string (stringSet
就是儲存 cities.txt
中所有名稱(且不重複)的 array),將其輸入 Xor filter
確認是否存在。
以下是分別針對 CPY 及 REF 測試的圖:
奇怪的地方是,兩張圖在 y = 0 ( x 軸) 處都仍有數據點分佈,理論上這些數據點代表不屬於 filter 的 string (直接出局不用到 tree 裡 search)。但所有輸入的 string 都屬於 filter,應該不會出現這種分佈。
關於 Bloom filter 的部份,benchmark程式碼如下:
以下是分別針對 CPY 及 REF 測試的圖:
到這邊可以知道我的 bench mark 設計一定有問題,需要再花時間去釐清。