map當中bool真的值得最佳化成struct{}{}嗎?

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

首先拋出一個問題,在Go中當我們想實現一個集合的時候,可以用map來實現.而map本身就可以通過”comma ok”機制來擷取該建是否存在,例如_ , ok := map["key"],如果沒有對應的值,ok為false,以此就可以實現集合.有時候我們會選擇map[string]bool這類方式來定義這個集合,但是因為有了”comma ok”這個文法,還可以定義成map[string]struct{}的形式,值不再佔用記憶體.

後者可以表示兩種狀態有或者無,而前者其實有三種狀態,有的時候表示true或者false,或者沒有.
很多時候我們會選擇map[string]struct{}來表示集合的實現,但是這樣真得值得麼?

這裡要從map的實現說起.map的實現是一個hash表.表結構由兩個結構體表示,分別是hmap和bmap,前者表示這個map結構,後者表示了map的hash表下的bucket結構.

一切要從map的實現開始講起.

map是由桶數組組成的,每個桶的表示如下.

// A bucket for a Go map.type bmap struct {        tophash [bucketCnt]uint8    // 這裡的bucetCnt是8,是個固定值,每個桶跟8個k-v對.    // 先是8個key,後是8個value.    // 最後是一個overflow指標指向串聯的bucket.}

而hmap表示如下,其實就是一個頭資訊.

// A header for a Go map.type hmap struct {        flags uint8 // 一些標誌j        B     uint8  // bucket數量的log_2        hash0 uint32 // hash 種子        buckets    unsafe.Pointer // buckets 數組的指標.        oldbuckets unsafe.Pointer // 增長時需要被替換的數組的指標.        nevacuate  uintptr        // 被提升的桶的數量(增長時,桶會從oldbuckets移到buckets當中)        overflow *[2]*[]*bmap // 指向串聯桶的指標.}

bmap這個結構類似於C的定義,後面其實還有一些成員,但是需要動態申請(runtime自己的malloc),沒有定義.
一個bmap會有8個位元組的tophash用於定位到桶中對應的entry.每個entry表示一個k-v,這個tophash是key的hash的高位位元組.
而定位桶用的是hash的低位位元組.在go中每個類型都會有自己的hash方法.

為了防止對齊問題,所以先排8個key,再排8個value.舉個例子如果是map[int8]int64,那麼k-v排在一起的話,就會空7個位元組,非常浪費.
但是先排8個int8的話就不會出現對齊的問題.最後一個結構是桶指標,指向串聯的桶.

而整個hmap是一個bmap的數組,主要是管理資訊.

記憶體分布.

hmap的增長是依賴於負載係數的,在go裡面負載係數(loadFactor)是6.5,這個值是一個通過測試得到的比較理想的一個值.
這個值的意思表示的是,每個桶平均裝下的entry數量是6.5個,之前我們提到了每個桶的大小是8.也就是說bucket一般都不會裝滿.

如果要負載係數高,也就是桶盡量裝滿,就會導致hash碰撞率較高(可以hash到的空間不大),這樣會產生過多的overflow的bucket.
如果要負載係數低,hash碰撞率比較低,這樣會使得空間很大,導致真正利用率(存入的資料/全部bucket空間)相對變小.
所以綜合情況負載係數6.5是一個比較理想的值,這也是go現在採用的值.

這個可以通過決定增長的關鍵代碼發現:

      for ; hint > bucketCnt && float32(hint) > loadFactor*float32(uintptr(1)<&ltB); B++ {        }

2^B是桶的數量,hint是申請的map的大小,bucketCnt就是8,因為預先會分配一個桶,如果一個桶都不會超過的話就不增加了.
關鍵是hint要保證大於負載係數*桶的數量,換句話說要保證平均每個桶裝6.5個k-v能容得下hint這麼多對k-v.
上面說得是靜態分配,動態增長的時候oldbuckets是buckets的一半,也就是翻倍增長.

hmap在增長的時候會把bueckets變成oldbuckets然後再申請新的buckets.buckets中的k-v是不會移動到別的桶當中去的.
這樣保證了遍曆時候的一致性.hashmap按照range遍曆的時候是按bucket數組的一個bucket開始然後bucket的串聯bucket再回到
bucket數組的下個元素依次遍曆.

刪除非常簡單,僅僅是把對應的key和value置為空白.

現在把map的實現說清楚以後我們可以算一筆賬.假設我們的map定義為map[string]struct{}{},
在64bit的作業系統下面一個桶的大小是 8 + 816 + 80 + 8 = 144個位元組(string 是常量只含一個指標和一個len值).
如果是map[string]bool{},那麼一個桶的大小是 8 + 816 + 81 = 152個位元組.

換算下來節省的空間大概是5.2%,考慮到負載係數是6.5,換成百分比是81.25%這個程度,省8個位元組的事情完全是多餘的.

與其犧牲語義取巧節省這幾個位元組不如定義一個表示清晰的map來的更直接.
所以我的結論是map[string]struct{}並不可取.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.