程式員進階篇之hash表的脾性

來源:互聯網
上載者:User

張炎潑先生於2016年加入白山雲科技,主要負責Object Storage Service研發、資料跨機房分布和修複問題解決等工作。以實現100PB級資料存放區為目標,其帶領團隊完成全網分布儲存系統的設計、實現與部署工作,將資料“冷”“熱”分離,使冷資料成本壓縮至1.2倍冗餘度。

張炎潑先生2006年至2015年,曾就職於新浪,負責Cross-IDC PB級雲端儲存體服務的架構設計、協作流程制定、代碼規範和實施標準制定及大部分功能實現等工作,支援新浪微博、微盤、視頻、SAE、音樂、軟體下載等新浪內部儲存等業務;2015年至2016年,於美團擔任進階技術專家,設計了跨機房的百PBObject Storage Service解決方案:設計和實現高並發和高可靠的多副本複製策略,最佳化Erasure Code降低90%IO開銷。

軟體開發中,一個hash表相當於把n個key隨機放入到b個bucket中,以實現n個資料在b個單位空間的儲存。

我們發現hash表中存在一些有趣現象:

hash表中key的分布規律

當hash表中key和bucket數量一樣時(n/b=1):

  • 37% 的bucket是空的

  • 37% 的bucket裡只有1個key

  • 26% 的bucket裡有1個以上的key(hash衝突)

直觀的展示了當n=b=20時,hash表裡每個bucket中key的數量(按照key的數量對bucket做排序):

往往我們對hash表的第一感覺是:如果將key隨機放入所有的bucket,bucket中key的數量較為均勻,每個bucket裡key數量的期望是1。

而實際上,bucket裡key的分布在n較小時非常不均勻;當n增大時,才會逐漸趨於平均。

key的數量對3類bucket數量的影響

下表表示當b不變,n增大時,n/b的值如何影響3類bucket的數量佔比(衝突率即含有多於1個key的bucket佔比):

更直觀一點,我們用來展示空bucket率和衝突率隨n/b值的變化趨勢:

key數量對bucket均勻程度的影響

上面幾組數字是當n/b較小時有意義的參考值,但隨n/b逐漸增大,空bucket與1個key的bucket數量幾乎為0,絕大多數bucket含有多個key。

當n/b超過1(1個bucket允許儲存多個key), 我們主要觀察的對象就轉變成bucket裡key數量的分布規律。

下表表示當n/b較大,每個bucket裡key的數量趨於均勻時,不均勻的程度是多少。
為了描述這種不均勻的程度,我們使用bucket中key數量的最大值和最小值之間的比例((most-fewest)/most)來表示。

下表列出了b=100時,隨n增大,key的分布情況。

可以看出,隨著bucket裡key平均數量的增加,其分布的不均勻程度也逐漸降低。

和空bucket或1個key的bucket的佔比不同n/b,均勻程度不僅取決於n/b的值,也受b值的影響,後面會提到。未使用統計中常用的均方差法去描述key分布的不均勻程度,是因為軟體開發過程中,更多時候要考慮最壞情況下所需準備的記憶體等資源。

Load Factor:n/b<0.75

hash表中常用一個概念 load factor α=n/b,來描述hash表的特徵。

通常,基於記憶體儲存的hash表,它的 n/b ≤0.75。這樣設定,既可節省空間的,也可以保持key的衝突率相對較低,低衝突率意味著低頻率的hash重定位,hash表的插入會更快。

線性探測是一個經常被使用的解決插入時hash衝突的演算法,它在1個bucket出現衝突時,按照逐步增加的步長順序向後查看這個bucket後面的bucket,直到找到1個空bucket。因此它對hash的衝突非常敏感。

在n/b=0.75 這個情境中,如果不使用線性探測(譬如使用bucket內的鏈表來儲存多個key),大約有47% 的bucket是空的;如果使用線性探測,這47%的bucket中,大約一半的bucket會被線性探測填充。

在很多記憶體hash表的實現中,選擇n/b=<=0.75作為hash表的容量上限,不僅是考慮到衝突率隨n/b增大而增大,更重要的是線性探測的效率會隨著n/b的增大而迅速降低。

hash表特性小貼士:

  • hash表本身是一個通過一定的空間浪費來換取效率的演算法。低時間開銷(O(1))、低空間浪費、低衝突率,三者不可同時兼得;

  • hash表只適合純記憶體資料結構的儲存:

    • hash表通過空間浪費換取了訪問速度的提升;磁碟的空間浪費是無法忍受的,但對記憶體的少許浪費是可接受的;

    • hash表只適合隨機訪問快的儲存介質。硬碟上的資料存放區更多使用btree或其他有序的資料結構。

  • 多數進階語言(內建hash table、hash set等)都保持 n/b≤<=0.75;

  • hash表在n/b較小時,不會均勻分配key。

Load Factor:n/b>1

另外一種hash表的實現,專門用來儲存比較多的key,當 n/b>1n/b1.0時,線性探測失效(沒有足夠的bucket儲存每個key)。這時1個bucket裡不僅儲存1個key,一般在一個bucket內用chaining,將所有落在這個bucket的key用鏈表串連起來,來解決衝突時多個key的儲存。

鏈表只在n/b不是很大時適用。因為鏈表的尋找需要O(n)的時間開銷,對於非常大的n/b,有時會用tree替代鏈表來管理bucket內的key。

n/b值較大的使用情境之一是:將一個網站的使用者隨機分配到多個不同的web-server上,這時每個web-server可以服務多個使用者。多數情況下,我們都希望這種分配能儘可能均勻,從而有效利用每個web-server資源。

這就要求我們關注hash的均勻程度。因此,接下來要討論的是,假定hash函數完全隨機的,均勻程度根據n和b如何變化。

n/b 越大,key的分布越均勻

當 n/b 足夠大時,空bucket率趨近於0,且每個bucket中key的數量趨於平均。每個bucket中key數量的期望是:

avg=n/b

定義一個bucket平均key的數量是100%:bucket中key的數量剛好是n/b,分別類比了 b=20,n/b分別為 10、100、1000時,bucket中key的數量分布。



可以看出,當 n/b 增大時,bucket中key數量的最大值與最小值差距在逐漸縮小。下表列出了隨b和n/b增大,key分布的均勻程度的變化:

結論:

計算

上述大部分結果來自於程式類比,現在我們來解決從數學上如何計算這些數值。

每類bucket的數量

空bucket數量

對於1個key, 它不在某個特定的bucket的機率是 (b−1)/b
所有key都不在某個特定的bucket的機率是( (b−1)/b)n

已知:

空bucket率是:

空bucket數量為:

有1個key的bucket數量

n個key中,每個key有1/b的機率落到某個特定的bucket裡,其他key以1-(1/b)的機率不落在這個bucket裡,因此,對某個特定的bucket,剛好有1個key的機率是:

剛好有1個key的bucket數量為:

多個key的bucket

剩下即為含多個key的bucket數量:

key在bucket中分布的均勻程度

類似的,1個bucket中剛好有i個key的機率是:n個key中任選i個,並都以1/b的機率落在這個bucket裡,其他n-i個key都以1-1/b的機率不落在這個bucket裡,即:

這就是著名的二項式分布。

我們可通過二項式分布估計bucket中key數量的最大值與最小值。

通過常態分佈來近似

當 n, b 都很大時,二項式分布可以用常態分佈來近似估計key分布的均勻性:

p=1/b,1個bucket中剛好有i個key的機率為:

1個bucket中key數量不多於x的機率是:

所以,所有不多於x個key的bucket數量是:

bucket中key數量的最小值,可以這樣估算: 如果不多於x個key的bucket數量是1,那麼這唯一1個bucket就是最少key的bucket。我們只要找到1個最小的x,讓包含不多於x個key的bucket總數為1, 這個x就是bucket中key數量的最小值。

計算key數量的最小值x

一個bucket裡包含不多於x個key的機率是:

Φ(x) 是常態分佈的累計分布函數,當x-μ趨近於0時,可以使用以下方式來近似:

這個函數的計算較難,但只是要找到x,我們可以在[0~μ]的範圍內逆向遍曆x,以找到一個x 使得包含不多於x個key的bucket期望數量是1。

x可以認為這個x就是bucket裡key數量的最小值,而這個hash表中,不均勻的程度可以用key數量最大值與最小值的差異來描述: 因為常態分佈是對稱的,所以key數量的最大值可以用 μ + (μ-x) 來表示。最終,bucket中key數量最大值與最小值的比例就是:

(μ是均值n/b)

程式類比

以下python指令碼類比了key在bucket中分布的情況,同時可以作為對比,驗證上述計算結果。

import sysimport mathimport timeimport hashlibdef normal_pdf(x, mu, sigma):    x = float(x)    mu = float(mu)    m = 1.0 / math.sqrt( 2 * math.pi ) / sigma    n = math.exp(-(x-mu)**2 / (2*sigma*sigma))return m * ndef normal_cdf(x, mu, sigma):    # integral(-oo,x)    x = float(x)    mu = float(mu)    sigma = float(sigma)    # to standard form    x = (x - mu) / sigma    s = x    v = x    for i in range(1, 100):        v = v * x * x / (2*i+1)        s += v    return 0.5 + s/(2*math.pi)**0.5 * math.e ** (-x*x/2)def difference(nbucket, nkey):    nbucket, nkey= int(nbucket), int(nkey)    # binomial distribution approximation by normal distribution    # find the bucket with minimal keys.    #    # the probability that a bucket has exactly i keys is:    #   # probability density function    #   normal_pdf(i, mu, sigma)    #    # the probability that a bucket has 0 ~ i keys is:    #   # cumulative distribution function    #   normal_cdf(i, mu, sigma)    #    # if the probability that a bucket has 0 ~ i keys is greater than 1/nbucket, we    # say there will be a bucket in hash table has:    # (i_0*p_0 + i_1*p_1 + ...)/(p_0 + p_1 + ..) keys.    p = 1.0 / nbucket    mu = nkey * p    sigma = math.sqrt(nkey * p * (1-p))    target = 1.0 / nbucket    minimal = mu    while True:        xx = normal_cdf(minimal, mu, sigma)        if abs(xx-target) < target/10:            break        minimal -= 1    return minimal, (mu-minimal) * 2 / (mu + (mu - minimal))def difference_simulation(nbucket, nkey):    t = str(time.time())    nbucket, nkey= int(nbucket), int(nkey)    buckets = [0] * nbucket    for i in range(nkey):        hsh = hashlib.sha1(t + str(i)).digest()        buckets[hash(hsh) % nbucket] += 1    buckets.sort()    nmin, mmax = buckets[0], buckets[-1]    return nmin, float(mmax - nmin) / mmaxif __name__ == "__main__":    nbucket, nkey= sys.argv[1:]    minimal, rate = difference(nbucket, nkey)    print 'by normal distribution:'    print '     min_bucket:', minimal    print '     difference:', rate    minimal, rate = difference_simulation(nbucket, nkey)    print 'by simulation:'    print '     min_bucket:', minimal    print '     difference:', rate

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.