1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
|
func evacuate(t *maptype, h *hmap, oldbucket uintptr) {
// 1) oldbucket 桶号对应的 *bmap
b := (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.bucketsize))) // *bmap
newbit := h.noldbuckets() // 旧桶数量,等量扩容就是 1<<h.B,翻倍扩容为 1<<(h.B-1)
// 2) 当前桶没有被迁移,tophash[0] not in (2,3,4)
if !evacuated(b) {
// TODO: reuse overflow buckets instead of using new ones, if there
// is no iterator using the old buckets. (If !oldIterator.)
//
// TODO: 如果没有迭代器使用旧的桶,则重用溢出的桶,而不是使用新的桶。(If !oldIterator)
// xy contains the x and y (low and high) evacuation destinations.
//
// xy包含x和y(low and high)疏散目的地。
// 2.1) 记录迁移后桶的去向,x与旧桶,y则是新桶
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))
// 2.2) 翻倍扩容
if !h.sameSizeGrow() {
// Only calculate y pointers if we're growing bigger.
// Otherwise GC can see bad pointers.
//
// 只有当y指针翻倍扩容时才计算。否则GC会看到坏指针。
y := &xy[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))
}
// 2.3) 遍历【常规桶】和后面的【溢出桶】
for ; b != nil; b = b.overflow(t) {
k := add(unsafe.Pointer(b), dataOffset) // key开始首地址
e := add(k, bucketCnt*uintptr(t.keysize)) // elem开始首地址
// 2.4) 遍历当前桶 i、k、e 分别对应当前桶 index、key、elem
// const bucketCnt = 8
for i := 0; i < bucketCnt; i, k, e = i+1, add(k, uintptr(t.keysize)), add(e, uintptr(t.elemsize)) {
top := b.tophash[i]
// 2.4.1) 当前i处为空,top <= 1
if isEmpty(top) {
// const evacuatedEmpty = 4
// 0或1 标记为 4
b.tophash[i] = evacuatedEmpty
continue
}
// const minTopHash = 5
if top < minTopHash {
throw("bad map state")
}
// 2.4.2) 有数据需要迁移
k2 := k // k是当前遍历桶&key unsafe.Pointer
if t.indirectkey() { // 间接存储
k2 = *((*unsafe.Pointer)(k2))
}
// 由于记录桶的流向 0.旧桶 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).
//
// 计算哈希值以做出疏散决策(我们需要将这个键/elem发送到桶x还是桶y)。
hash := t.hasher(k2, uintptr(h.hash0))
// !t.reflexivekey() == true 说明 k2 != k2 成立,比如 NaN
// h.flags&iterator != 0 迭代器在扩容后
if h.flags&iterator != 0 && !t.reflexivekey() && !t.key.equal(k2, k2) {
// If key != key (NaNs), then the hash could be (and probably
// will be) entirely different from the old hash. Moreover,
// it isn't reproducible. Reproducibility is required in the
// presence of iterators, as our evacuation decision must
// match whatever decision the iterator made.
// Fortunately, we have the freedom to send these keys either
// way. Also, tophash is meaningless for these kinds of keys.
// We let the low bit of tophash drive the evacuation decision.
// We recompute a new random tophash for the next level so
// these keys will get evenly distributed across all buckets
// after multiple grows.
//
// 如果 key != key (NaNs),那么这个hash值可能(也可能会)与旧的hash值完全不同。此外,它是不可复制的。
// 在迭代器存在的情况下,需要重现性,因为我们的疏散决策必须匹配迭代器所做的任何决策。
// 幸运的是,我们可以自由地以任何方式发送这些keys。而且,tophash对于这种类型的键没有意义。(因为无法取出来)
// 我们让低bit位的tophash来决定疏散。
// 我们为下一层重新计算一个新的随机tophash值,这样这些键在增长多次后将均匀分布到所有桶中。
// 注意这里的top是旧桶的tophash在计算疏散方向
useY = top & 1 // top的最低位用来随机计算,以便均匀的分布这些key到存储桶中去
top = tophash(hash) // 从新计算tophash,该top存储在新桶
} else {
// 没有迭代器在运行 或 k == k成立
// 流向新桶,newbit是旧桶数量为2的幂次方
if hash&newbit != 0 {
useY = 1
}
}
}
// const evacuatedX = 2
// const evacuatedY = 3
if evacuatedX+1 != evacuatedY || evacuatedX^1 != evacuatedY {
throw("bad evacuatedN")
}
// tophash = 2 或 3,数据流向;
// 注意上面的 useY = top & 1;
// 即使这里修改了旧桶的topHash也能知道疏散方向,因为2和3代表疏散方向
b.tophash[i] = evacuatedX + useY // evacuatedX + 1 == evacuatedY
// dst目标桶
dst := &xy[useY] // evacuation destination
// 当前桶已经存满了
// const bucketCnt = 8
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 // mask dst.i as an optimization, to avoid a bounds check
if t.indirectkey() { // 间接存储
*(*unsafe.Pointer)(dst.k) = k2 // copy pointer
} else {
typedmemmove(t.key, dst.k, k) // copy elem
}
if t.indirectelem() { // 间接存储
*(*unsafe.Pointer)(dst.e) = *(*unsafe.Pointer)(e)
} else {
typedmemmove(t.elem, dst.e, e)
}
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))
}
}
// Unlink the overflow buckets & clear key/elem to help GC.
//
// 当前桶及溢出桶都被疏散完成后;断开溢出桶 并 清除key/elem以帮助GC
// h.flags&oldIterator == 0:遍历器没有在遍历旧数据
// t.bucket.ptrdata != 0:桶类型中存在指针,也就是 key/elem 类型中存在指针
if h.flags&oldIterator == 0 && t.bucket.ptrdata != 0 {
// 当前清除旧桶所在桶地址
b := add(h.oldbuckets, oldbucket*uintptr(t.bucketsize))
// Preserve b.tophash because the evacuation
// state is maintained there.
//
// 保留b.tophash,因为疏散状态在那里保持。
ptr := add(b, dataOffset) // key开始地址
n := uintptr(t.bucketsize) - dataOffset // 需要清除的字节长度
// 清除keys、elems、overflow帮助GC
memclrHasPointers(ptr, n)
}
}
// growWork方法在迁移了本次桶后会再迁移一次h.nevacuate,因此这里得到执行
if oldbucket == h.nevacuate {
advanceEvacuationMark(h, t, newbit)
}
}
|