<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
為什麼要搬遷?無非是要麼桶用的太多,要麼太多的資料都到了overflow裡面了
go針對這兩種情況做出了不同的搬遷處理
以下程式碼基於go1.17
桶用太多
go用了一個負載因子loadFactor來衡量。也就是如果數量count大於loadFactor * bucket數,那麼就要擴容
程式碼如下
const ( // Maximum number of key/elem pairs a bucket can hold. bucketCntBits = 3 bucketCnt = 1 << bucketCntBits // Maximum average load of a bucket that triggers growth is 6.5. // Represent as loadFactorNum/loadFactorDen, to allow integer math. loadFactorNum = 13 loadFactorDen = 2 ) // 在元素數量大於8且元素數量大於負載因子(6.5)*桶總數,就要進行擴容 func overLoadFactor(count int, B uint8) bool { return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen) }
overflow太多
overflow太多在go中分兩種情況
// overflow buckets 太多 func tooManyOverflowBuckets(noverflow uint16, B uint8) bool { if B > 15 { B = 15 } return noverflow >= uint16(1)<<(B&15) }
準備搬遷工作是由hashGrow方法完成的,他主要就是進行申請新buckets空間、初始化搬遷進度為0、將原桶標記為舊桶等
func hashGrow(t *maptype, h *hmap) { // 判斷是bucket多還是overflow多,然後根據這兩種情況去申請新buckets空間 bigger := uint8(1) if !overLoadFactor(h.count+1, h.B) { bigger = 0 h.flags |= sameSizeGrow } oldbuckets := h.buckets newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil) flags := h.flags &^ (iterator | oldIterator) if h.flags&iterator != 0 { flags |= oldIterator } // commit the grow (atomic wrt gc) // 更新最新的bucket總數、將原桶標記為舊桶(後面判斷是否在搬遷就是通過這個進行判斷的) h.B += bigger h.flags = flags h.oldbuckets = oldbuckets h.buckets = newbuckets // 初始化搬遷進度為0 h.nevacuate = 0 // 初始化新桶overflow數量為0 h.noverflow = 0 // 將原來extra的overflow掛載到old overflow,代表這已經是舊的了 if h.extra != nil && h.extra.overflow != nil { // Promote current overflow buckets to the old generation. if h.extra.oldoverflow != nil { throw("oldoverflow is not nil") } h.extra.oldoverflow = h.extra.overflow h.extra.overflow = nil } // extra指向下一塊空閒的overflow空間 if nextOverflow != nil { if h.extra == nil { h.extra = new(mapextra) } h.extra.nextOverflow = nextOverflow } }
正式搬遷其實是在新增、刪除元素的時候順便乾的。在發現搬遷的時候,就可能會做一到兩次的搬遷,每次搬遷一個bucket。具體是一次還是兩次就是下面的邏輯決定的
搬遷正在使用的bucket對應old bucket(如果沒有搬遷過的話)
若還正在搬遷就再搬一個以加快搬遷
func growWork(t *maptype, h *hmap, bucket uintptr) { // make sure we evacuate the oldbucket corresponding // to the bucket we're about to use evacuate(t, h, bucket&h.oldbucketmask()) // evacuate one more oldbucket to make progress on growing if h.growing() { evacuate(t, h, h.nevacuate) } }
對於不擴容的情況,可能只有一個——就是原來序號對應的桶(就是下面的x)。
對於擴容2倍的情況,顯然既有可能是在原來序號對應桶,也有可能是原來序號加上擴容的桶數的序號
比如B由2變成了3,那麼就要看hash第3bit到底是0還是1了,如果是001,那麼和原來的一樣,是序號為1的桶;如果是101,那麼就是原來序號1+22 (擴容桶數)=序號為5的桶
// xy contains the x and y (low and high) evacuation destinations. var xy [2]evacDst x := &xy[0] x.b = (*bmap)(add(h.buckets, oldbucket*uintptr(t.bucketsize))) x.k = add(unsafe.Pointer(x.b), dataOffset) x.e = add(x.k, bucketCnt*uintptr(t.keysize)) if !h.sameSizeGrow() { // Only calculate y pointers if we're growing bigger. // Otherwise GC can see bad pointers. y := &xy[1] // newBit在擴容的情況下等於1<<(B-1) y.b = (*bmap)(add(h.buckets, (oldbucket+newbit)*uintptr(t.bucketsize))) y.k = add(unsafe.Pointer(y.b), dataOffset) y.e = add(y.k, bucketCnt*uintptr(t.keysize)) }
每一個bucket在溢位之後都會附接overflow桶,每個bucket包括overflow儲存著8個元素
在上面的步驟計算hash值在overflow用太多的情況下是不用的
此外,在桶用太多的情況下,計算hash
for ; b != nil; b = b.overflow(t) { // 找到key開始位置k,和value開始位置e k := add(unsafe.Pointer(b), dataOffset) e := add(k, bucketCnt*uintptr(t.keysize)) // 遍歷bucket中元素進行搬遷 for i := 0; i < bucketCnt; i, k, e = i+1, add(k, uintptr(t.keysize)), add(e, uintptr(t.elemsize)) { // 獲取tophash,若發現是空,說明已經搬遷過。並標記為空且已搬遷 top := b.tophash[i] if isEmpty(top) { b.tophash[i] = evacuatedEmpty continue } if top < minTopHash { throw("bad map state") } k2 := k // 若key為參照型別就解除參照 if t.indirectkey() { k2 = *((*unsafe.Pointer)(k2)) } // X指的就是原序號桶 // Y指的就是擴容情況下,新的最高位為1的時候應該去的桶 var useY uint8 if !h.sameSizeGrow() { // Compute hash to make our evacuation decision (whether we need // to send this key/elem to bucket x or bucket y). hash := t.hasher(k2, uintptr(h.hash0)) // 若正在迭代,且key為NaNs。是不是使用Y就取決最低位是不是1了 if h.flags&iterator != 0 && !t.reflexivekey() && !t.key.equal(k2, k2) { useY = top & 1 top = tophash(hash) } else { // 如果最高位為1,那麼就應該選Y桶 if hash&newbit != 0 { useY = 1 } } } if evacuatedX+1 != evacuatedY || evacuatedX^1 != evacuatedY { throw("bad evacuatedN") } // 標記X還是Y搬遷,並依此獲取到搬遷目標桶 b.tophash[i] = evacuatedX + useY dst := &xy[useY] // 若目標桶已經超出桶容量,就分配新overflow if dst.i == bucketCnt { dst.b = h.newoverflow(t, dst.b) dst.i = 0 dst.k = add(unsafe.Pointer(dst.b), dataOffset) dst.e = add(dst.k, bucketCnt*uintptr(t.keysize)) } // 更新元素目標桶對應的tophash // 採用與而非取模,應該是出於效能目的 dst.b.tophash[dst.i&(bucketCnt-1)] = top // 複製key到目標桶 if t.indirectkey() { *(*unsafe.Pointer)(dst.k) = k2 // copy pointer } else { typedmemmove(t.key, dst.k, k) // copy elem } // 複製value到目標桶 if t.indirectelem() { *(*unsafe.Pointer)(dst.e) = *(*unsafe.Pointer)(e) } else { typedmemmove(t.elem, dst.e, e) } // 更新目標桶元素數量、key、value位置 dst.i++ // These updates might push these pointers past the end of the key or elem arrays. // That's ok, as we have the overflow pointer at the end of the bucket to protect against pointing past the end of the bucket. dst.k = add(dst.k, uintptr(t.keysize)) dst.e = add(dst.e, uintptr(t.elemsize)) } }
如果發現沒有在使用舊buckets的就把原buckets中的key-value清理掉吧(tophash還是保留用來搬遷時判斷狀態)
// Unlink the overflow buckets & clear key/elem to help GC. if h.flags&oldIterator == 0 && t.bucket.ptrdata != 0 { // 計算當前原bucket所在的開始位置b b := add(h.oldbuckets, oldbucket*uintptr(t.bucketsize)) // Preserve b.tophash because the evacuation // state is maintained there. // 從開始位置加上key-value的偏移量,那麼ptr所在的位置就是實際key-value的開始位置 ptr := add(b, dataOffset) // n是總bucket大小減去key-value的偏移量,就key-value佔用空間的大小 n := uintptr(t.bucketsize) - dataOffset // 清理從ptr開始的n個位元組 memclrHasPointers(ptr, n) }
在本次搬遷進度是最新進度的情況下(不是最新就讓最新的去幹吧)
func advanceEvacuationMark(h *hmap, t *maptype, newbit uintptr) { // 更新進度 h.nevacuate++ // Experiments suggest that 1024 is overkill by at least an order of magnitude. // Put it in there as a safeguard anyway, to ensure O(1) behavior. // 向後看,更新已經完成的進度 stop := h.nevacuate + 1024 if stop > newbit { stop = newbit } for h.nevacuate != stop && bucketEvacuated(t, h, h.nevacuate) { h.nevacuate++ } // 若是完成搬遷,就釋放掉old buckets、重置搬遷狀態、釋放原bucket掛載到extra的overflow指標 if h.nevacuate == newbit { // newbit == # of oldbuckets // Growing is all done. Free old main bucket array. h.oldbuckets = nil // Can discard old overflow buckets as well. // If they are still referenced by an iterator, // then the iterator holds a pointers to the slice. if h.extra != nil { h.extra.oldoverflow = nil } h.flags &^= sameSizeGrow } }
到此這篇關於go map搬遷的實現的文章就介紹到這了,更多相關go map搬遷內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45