Go的map迁移过程是渐进式迁移,其实这样做的目的就是为了防止大面积的迁移导致的性能问题。如果如果一次性进行全部迁移有可能会发生系能抖动的情况
Go的渐进式迁移是怎么做的呢?look:
一般情况下,读写删的时候都有可能触发渐进式库容。go map底层中,每次迁移只会迁移2个bucket。
// t map的类型信息
// h 当前hmap
// bucket 当前需要操作的key所在的桶
func growWork(t *maptype, h *hmap, bucket uintptr) {
// make sure we evacuate the oldbucket corresponding
// to the bucket we're about to use
// 迁移函数evacuate
evacuate(t, h, bucket&h.oldbucketmask())
// evacuate one more oldbucket to make progress on growing
if h.growing() {
evacuate(t, h, h.nevacuate)
}
}
bucket&h.oldbucketmask() 这个方法相当于h.oldbucketmask() == 1<<(B-1) - 1。这里是用来定位到oldbucket的位置。
其实这里我是有疑问的。如果是增量扩容的况下B是+1的。因为key通过hash算法之后的到一个64位的无符号整数。这个时候就出现一个问题,没有扩容之前取的是后B位确定桶,高8位确定在桶的那个位置(cell)。但是发生扩容之后B+1了,桶的位置发生了变化。然后要进行迁移,这个时候要怎么去确定当前key在老桶的位置呢?
那就看看吧:
假设key,通过hash算法算出的值为:
十进制:0xc404f767de07a98c
二进制:1100010000000100111101110110011111011110000001111010100110001100
oldbucket长度:2^3 = 8,B = 3 ,0 - 7
newbucket长度:2^4 = 16,B = 4,0 - 15
桶掩码:
oldbucketmask = 2^(B-1) = 7 = 0b0111
newbucketmask = 2^(B-1) = 15 = 0b1111
其实有点类似桶的下标index从0开始计算
这个时候定位桶的位置,取后B位就是100然后与oldbucketmash做&运算,计算出4号桶
0b0111 = oldbucketmash
0b0100 = key的后B位
------
0b0100
0b0100 = 4
这个时候发生增量扩容的情况,在查询到这个key的时候,发现他在老桶上并没有迁移到新桶上。这个时候就开始进行渐进式迁移。首先底层代码会判断在新桶上还是老桶上(这个后边说)。因为发生了扩容,B的值肯定是会变化的,这个时候要怎么去老桶上找呢?look:
- 定位key在老桶的位置
- 确定后B位置,因为扩容是翻倍的,所以只需要进行2^(B-1)
- 然后就是上面的运算过程,就知道在4号桶中
- 但是这个时候我们要迁移了
扩容后B = 4
所以我们要去后4位去计算当前key在新桶的位置,后四位为 1100
0b1111 = newbucketmash
0b1100 = key的后4位
------
ob1100 = 12
这个时候就可以迁移到第12个桶中了
另一种情况:
假设一个hash出来的值为hash = 0b10110
hash = 0b10110
B = 3 取hash后3位
在老桶中为:
0b0111 = oldbucketmash
0b0110 = hash
------
0b0110 = 6 在桶6中
扩容迁移
B = 4 取hash后4位
0b1111 = newbucketmash
0b0110 = hash
------
0b0110 = 6 在桶6中
其实还有一个算法确定新桶的位置
0b1000 = 1 << 3(oldB) = 8
0b0110 = hahs
------
0b0000
从左往右数,从0开始,第oldB位:
如果为0那就是key要迁过去的新桶的位置和老桶的桶号是一样
如果是1那就是key所在新桶的桶号为:X+2^B = 12 也就是6 + 8 = 14
X对应的就是新桶和旧桶的位置,因为是翻倍扩容的所以1:2一个旧桶对应两个新桶
新桶 A = X
新桶 B = X + (1 << oldB)
根据以上的公式:X = 6 ,(1 << oldB) = 8 = 2^3
所以就等于12
在举个例子:
shah = 0b11110
oldB = 3 = 8 bucket
newB = 4 = 16 bucket
旧桶的位置:
oldbcket = shah & ((1 << 3) -1)
((1 << 3) -1) = 0b111
shah = 0b11110
0b111
0b110
-------
0b110 对用就是6号桶和直接取shah的后B位是一样
发生扩容:
B = 4
newbit = (1 << 3) = 0b1000
hash = 0b11110
0b1000
0b1110
-------
0b1000 = newbit 这里从右往左数ondB位从0开始也就是1所以根据
X + (1 << oldB) = 6 + 8 = 12
12就是这个key所在桶的桶号
查找hash = 0b11110
现在B = 4
取后B位置:1110 = 12
以上为个人理解,可能有错的地方希望大佬们可以指出来,谢谢各位。