過濾器系列(二) Cuckoo filter

2022-03-08 05:22:32 字數 3163 閱讀 8892

這一篇講的是布穀過濾器(cuckoo fliter),這個名字**於更早發表的布穀雜湊(cuckoo hash),儘管我也不知道為什麼當初要給這種雜湊表起個鳥名=_=

由於布穀過濾器本身的思想就源自於布穀雜湊,那麼我們就從布穀雜湊開始說它的設計思想。產生布穀雜湊表的乙個重要背景是人們對於球盒問題的分析:給定n個球,隨機的放在n個盒子裡,在裝球最多的盒子裡,球的個數的期望是多少?答案是\(\theta (logn/loglogn)\),這個問題其實就是雜湊表裝填因子為1時的情況分析。後來有一天,人們發現:每次放球的時候,如果隨機選擇兩個盒子,將球放到當時較空的那個盒子裡,那麼這個期望就變成了\(\theta (loglogn)\),這個界小於之前的界,這給了布穀雜湊表作者啟發。

乙個布穀雜湊表通常有兩張(一般來說)表,分別有乙個對應的hash函式,當有新的項插入的時候,它會計算出這個項在兩張表中對應的兩個位置,這個項一定會被存在這兩個位置之一,而具體是哪乙個卻不確定。

下圖是乙個布穀雜湊表的初始化示意圖:

現在我們假設有一些項要存入雜湊表,其每個項都有其對應的兩個位置,先插入第一項a

由於插入a的時候其兩個候選位置(0,2)都沒有占用,所以選擇第一張表或者是第二張表都可以,我們在這裡預設先選擇第一張表,然後插入第二項b

我們看到原來的a的位置被b占用,而a被「踢」到它的備選位置表二的2號位置上了,這就是當發生位置衝突時,布穀雜湊表的處理邏輯,後來的資料項將會把之前占用的項踢到另乙個位置上。我們接下來插入第三項c

沒有衝突,順利搞定,接著插入d

d成功的把c踢走了,其實看到這裡讀者應該在猜想,會不會有一種情況,即被踢走的資料的另乙個備選位置也被占用了,這樣怎麼辦?答案是繼續踢,乙個踢乙個,直到大家都找到自己合適的歸宿為止。那麼如果發現出現了迴圈怎麼辦?答案是gg,這代表布穀雜湊錶走到了極限

插入e這裡就發生了多次替換的情況,f代替了e,e代替了a,a代替了b,b找到了空餘的位子

讀者可以考慮一下,如果這個時候要想插入乙個「g」,其備選位置是1,2,這樣的話會出現什麼情況?

好了,布穀雜湊表大概介紹完了,現在該布穀過濾器了。布穀過濾器的主要也是通過布穀雜湊來實現的,其主要變化是:

1.我們不將原來的資料完整的存進來,作為過濾器,當然要省點空間,選用的辦法設計乙個指紋,將比較大的原資料變成了乙個個指紋串,這樣就大大節省了空間。

2.由於第一點所說,儲存的不是原資料,那麼在替換位置的時候,我們需要再次計算原來的資料的備選位置,這樣一來布穀雜湊表的方法就失效了。在這裡,作者設計了乙個方法,他將兩個hash函式變成了乙個hash函式,第一張表的備選位置是hash(x),第二張表的備選位置是\(hash(x) \oplus hash(fingerprint(x))\),即第一張表的位置與儲存的指紋的hash值做異或運算。這樣做的優點就是不用再另外儲存元素的備選位置,在替換時,可以直接用異或來計算出其另乙個備選位置。(讀者可以想想如何通過表二的位置計算出元素在表一中的位置)

3.插入時,優先選擇空位置,而不是盡可能的踢走其他元素。

插入偽**如下:

algorithm 1: insert(x)

f = fingerprint(x)

i1 = hash(x)

i2 = i1 xor hash(f)

if bucket[i1] or bucket[i2] has an empty entry then

//只要有空位就先插入空位裡

add f to that bucket

return done

i = randomly pick i1 or i2

for n=0;n查詢偽**如下:

algorithm 2: lookup(x)

f = fingerprint(x)

i1 = hash(x)

i2 = i1 xor hash(f)

if bucket[i1] or bucket[i2] has f then

return true

return false

刪除偽**如下:

algorithm 3: delete(x)

f = fingerprint(x)

i1 = hash(x)

i2 = i1 xor hash(f)

if bucket[i1] or bucket[i2] has f then

remove a copy of f from this bucket

return false

刪除這部分值得注意,當被刪除元素的另乙個備選位置有其他元素的指紋的時候,我們不能確定到底哪乙個是要刪除的元素,其實我們也不去關心到底是不是要刪除的元素,我們直接刪除掉其中的乙個。這樣一來,我們其實並沒有真正的刪除掉元素,為什麼這麼說,因為當你刪除掉這個元素的時候我們再查詢這個元素,按照演算法來看我們還是一樣能檢索出來這個元素在我們的布穀過濾器裡,這就是假陽率的其中乙個**(還有乙個重要**是指紋構造的重複,即多個元素產生相同指紋)

下面我們來分析一下布穀過濾器的平均每個元素占用的位元數,設每個桶裡裝\(b\)個指紋,要求錯誤率的上界為\(\epsilon\),\(f\)為指紋長度。

那麼這個上界要求小於要求的上界,即\(2b/2^f \le \epsilon\),得到

\(f \ge log_2^ = log_2^ + log_2^\)

則平均每個元素占用的位元數為\(c \le (log_2^ + log_2^) / \alpha\)

在原**中,作者其實後面還做了乙個比較強行的優化,在此不提,後面設計其他過濾器的作者也沒有把這個優化算數。。。。不過作者提到了在實際測試中,他們發現當b=4的時候是空間效能最好的情況,所以一般說來,我們直接把b當做常數4,代入到前面算出來的公式中,\(c \le (log_2^ + 3) / \alpha\)

布穀過濾器就說到這,布穀過濾器在錯誤率小於3%的時候空間效能是優於布隆過濾器的,而這個條件在實際應用中常常滿足,所以一般來說它的空間效能是要優於布隆過濾器的。而且,布穀過濾器按照普通設計,只有兩個桶,在查詢的時候可以確保兩次訪存就可以做完,相比於布隆過濾器的k個hash函式k次訪存,在資料量很大不能全部裝載在記憶體中的情況下,多一次訪存那麼時間上就輸了。不過說完優點,布穀過濾器也有其相應的缺點,當裝填因子較高的時候,容易出現迴圈的問題,即插入失敗的情況,到這時就很難辦。另外,它還有跟布隆過濾器共有的乙個缺點,就是訪問空間位址不連續,通常可以認為是隨機的,這樣嚴重破壞了程式區域性性,對於cache流水線來說非常不利,這為之後的過濾器設計埋下了乙個伏筆。

hbase 過濾器 scala 過濾器系列

過濾器系列710 c30810 賓士 c64 1500 004 09411 04 004094 3504 h12 110 2 w11102 2 wdk724 wdk725 沃爾沃 3825778 8149064 3825133 3825215 466634 11110668 11711074 477...

Revit二開 過濾器之選擇過濾器

滑鼠在選擇的時候通過乙個過濾器就可以篩選指定類別的元素,那麼我在用滑鼠點選的時候只要選擇牆,怎麼做呢。案例如下 var ele sel.pickobject objecttype.element,要新增的過濾器 getelement doc 過濾器定義 public class wallfilter...

Vue 過濾器案例(全域性過濾器和區域性過濾器)

doctype html en utf 8 viewport content width device width,initial scale 1.0 js vue 2.4.0 js script 過濾器 title head 兩個過濾器的名稱都為msgformat,但是控制不同作用,乙個是全域性的...