Skip to content

Commit 85e7bee

Browse files
committed
runtime: do not scan maps when k/v do not contain pointers
Currently we scan maps even if k/v does not contain pointers. This is required because overflow buckets are hanging off the main table. This change introduces a separate array that contains pointers to all overflow buckets and keeps them alive. Buckets themselves are marked as containing no pointers and are not scanned by GC (if k/v does not contain pointers). This brings maps in line with slices and chans -- GC does not scan their contents if elements do not contain pointers. Currently scanning of a map[int]int with 2e8 entries (~8GB heap) takes ~8 seconds. With this change scanning takes negligible time. Update #9477. Change-Id: Id8a04066a53d2f743474cad406afb9f30f00eaae Reviewed-on: https://go-review.googlesource.com/3288 Reviewed-by: Keith Randall <[email protected]>
1 parent 561ce92 commit 85e7bee

File tree

3 files changed

+110
-35
lines changed

3 files changed

+110
-35
lines changed

src/cmd/gc/reflect.c

+41-17
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,10 @@ mapbucket(Type *t)
181181
valuesfield->down = overflowfield;
182182
overflowfield->down = T;
183183

184+
// See comment on hmap.overflow in ../../runtime/hashmap.go.
185+
if(!haspointers(t->type) && !haspointers(t->down))
186+
bucket->haspointers = 1; // no pointers
187+
184188
bucket->width = offset;
185189
bucket->local = t->local;
186190
t->bucket = bucket;
@@ -197,7 +201,7 @@ static Type*
197201
hmap(Type *t)
198202
{
199203
Type *h, *bucket;
200-
Type *bucketsfield, *oldbucketsfield;
204+
Type *bucketsfield, *oldbucketsfield, *overflowfield;
201205
int32 offset;
202206

203207
if(t->hmap != T)
@@ -208,9 +212,10 @@ hmap(Type *t)
208212
h->noalg = 1;
209213

210214
offset = widthint; // count
211-
offset += 4; // flags
212-
offset += 4; // hash0
215+
offset += 1; // flags
213216
offset += 1; // B
217+
offset += 2; // padding
218+
offset += 4; // hash0
214219
offset = (offset + widthptr - 1) / widthptr * widthptr;
215220

216221
bucketsfield = typ(TFIELD);
@@ -227,12 +232,20 @@ hmap(Type *t)
227232
oldbucketsfield->sym->name = "oldbuckets";
228233
offset += widthptr;
229234

230-
offset += widthptr; // nevacuate (last field in Hmap)
235+
offset += widthptr; // nevacuate
236+
237+
overflowfield = typ(TFIELD);
238+
overflowfield->type = types[TUNSAFEPTR];
239+
overflowfield->width = offset;
240+
overflowfield->sym = mal(sizeof(Sym));
241+
overflowfield->sym->name = "overflow";
242+
offset += widthptr;
231243

232244
// link up fields
233245
h->type = bucketsfield;
234246
bucketsfield->down = oldbucketsfield;
235-
oldbucketsfield->down = T;
247+
oldbucketsfield->down = overflowfield;
248+
overflowfield->down = T;
236249

237250
h->width = offset;
238251
h->local = t->local;
@@ -245,7 +258,7 @@ Type*
245258
hiter(Type *t)
246259
{
247260
int32 n, off;
248-
Type *field[7];
261+
Type *field[9];
249262
Type *i;
250263

251264
if(t->hiter != T)
@@ -259,6 +272,7 @@ hiter(Type *t)
259272
// h *Hmap
260273
// buckets *Bucket
261274
// bptr *Bucket
275+
// overflow unsafe.Pointer
262276
// other [4]uintptr
263277
// }
264278
// must match ../../runtime/hashmap.c:hash_iter.
@@ -292,29 +306,39 @@ hiter(Type *t)
292306
field[5]->sym = mal(sizeof(Sym));
293307
field[5]->sym->name = "bptr";
294308

295-
// all other non-pointer fields
296309
field[6] = typ(TFIELD);
297-
field[6]->type = typ(TARRAY);
298-
field[6]->type->type = types[TUINTPTR];
299-
field[6]->type->bound = 4;
300-
field[6]->type->width = 4 * widthptr;
310+
field[6]->type = types[TUNSAFEPTR];
301311
field[6]->sym = mal(sizeof(Sym));
302-
field[6]->sym->name = "other";
312+
field[6]->sym->name = "overflow0";
313+
314+
field[7] = typ(TFIELD);
315+
field[7]->type = types[TUNSAFEPTR];
316+
field[7]->sym = mal(sizeof(Sym));
317+
field[7]->sym->name = "overflow1";
318+
319+
// all other non-pointer fields
320+
field[8] = typ(TFIELD);
321+
field[8]->type = typ(TARRAY);
322+
field[8]->type->type = types[TUINTPTR];
323+
field[8]->type->bound = 4;
324+
field[8]->type->width = 4 * widthptr;
325+
field[8]->sym = mal(sizeof(Sym));
326+
field[8]->sym->name = "other";
303327

304328
// build iterator struct holding the above fields
305329
i = typ(TSTRUCT);
306330
i->noalg = 1;
307331
i->type = field[0];
308332
off = 0;
309-
for(n = 0; n < 6; n++) {
333+
for(n = 0; n < nelem(field)-1; n++) {
310334
field[n]->down = field[n+1];
311335
field[n]->width = off;
312336
off += field[n]->type->width;
313337
}
314-
field[6]->down = T;
315-
off += field[6]->type->width;
316-
if(off != 10 * widthptr)
317-
yyerror("hash_iter size not correct %d %d", off, 10 * widthptr);
338+
field[nelem(field)-1]->down = T;
339+
off += field[nelem(field)-1]->type->width;
340+
if(off != 12 * widthptr)
341+
yyerror("hash_iter size not correct %d %d", off, 11 * widthptr);
318342
t->hiter = i;
319343
i->map = t;
320344
return i;

src/reflect/type.go

+9-3
Original file line numberDiff line numberDiff line change
@@ -1469,9 +1469,8 @@ func MapOf(key, elem Type) Type {
14691469

14701470
// Make a map type.
14711471
var imap interface{} = (map[unsafe.Pointer]unsafe.Pointer)(nil)
1472-
prototype := *(**mapType)(unsafe.Pointer(&imap))
14731472
mt := new(mapType)
1474-
*mt = *prototype
1473+
*mt = **(**mapType)(unsafe.Pointer(&imap))
14751474
mt.string = &s
14761475
mt.hash = fnv1(etyp.hash, 'm', byte(ktyp.hash>>24), byte(ktyp.hash>>16), byte(ktyp.hash>>8), byte(ktyp.hash))
14771476
mt.key = ktyp
@@ -1575,7 +1574,7 @@ func (gc *gcProg) appendProg(t *rtype) {
15751574
for i := 0; i < c; i++ {
15761575
gc.appendProg(t.Field(i).Type.common())
15771576
}
1578-
if gc.size > oldsize + t.size {
1577+
if gc.size > oldsize+t.size {
15791578
panic("reflect: struct components are larger than the struct itself")
15801579
}
15811580
gc.size = oldsize + t.size
@@ -1650,6 +1649,12 @@ const (
16501649
)
16511650

16521651
func bucketOf(ktyp, etyp *rtype) *rtype {
1652+
// See comment on hmap.overflow in ../runtime/hashmap.go.
1653+
var kind uint8
1654+
if ktyp.kind&kindNoPointers != 0 && etyp.kind&kindNoPointers != 0 {
1655+
kind = kindNoPointers
1656+
}
1657+
16531658
if ktyp.size > maxKeySize {
16541659
ktyp = PtrTo(ktyp).(*rtype)
16551660
}
@@ -1679,6 +1684,7 @@ func bucketOf(ktyp, etyp *rtype) *rtype {
16791684

16801685
b := new(rtype)
16811686
b.size = gc.size
1687+
b.kind = kind
16821688
b.gc[0], _ = gc.finalize()
16831689
s := "bucket(" + *ktyp.string + "," + *etyp.string + ")"
16841690
b.string = &s

src/runtime/hashmap.go

+60-15
Original file line numberDiff line numberDiff line change
@@ -106,13 +106,24 @@ type hmap struct {
106106
// Note: the format of the Hmap is encoded in ../../cmd/gc/reflect.c and
107107
// ../reflect/type.go. Don't change this structure without also changing that code!
108108
count int // # live cells == size of map. Must be first (used by len() builtin)
109-
flags uint32
110-
hash0 uint32 // hash seed
109+
flags uint8
111110
B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
111+
hash0 uint32 // hash seed
112112

113113
buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
114114
oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
115115
nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
116+
117+
// If both key and value do not contain pointers, then we mark bucket
118+
// type as containing no pointers. This avoids scanning such maps.
119+
// However, bmap.overflow is a pointer. In order to keep overflow buckets
120+
// alive, we store pointers to all overflow buckets in hmap.overflow.
121+
// Overflow is used only if key and value do not contain pointers.
122+
// overflow[0] contains overflow buckets for hmap.buckets.
123+
// overflow[1] contains overflow buckets for hmap.oldbuckets.
124+
// The first indirection allows us to reduce static size of hmap.
125+
// The second indirection allows to store a pointer to the slice in hiter.
126+
overflow *[2]*[]*bmap
116127
}
117128

118129
// A bucket for a Go map.
@@ -135,6 +146,7 @@ type hiter struct {
135146
h *hmap
136147
buckets unsafe.Pointer // bucket ptr at hash_iter initialization time
137148
bptr *bmap // current bucket
149+
overflow [2]*[]*bmap // keeps overflow buckets alive
138150
startBucket uintptr // bucket iteration started at
139151
offset uint8 // intra-bucket offset to start from during iteration (should be big enough to hold bucketCnt-1)
140152
wrapped bool // already wrapped around from end of bucket array to beginning
@@ -152,10 +164,24 @@ func evacuated(b *bmap) bool {
152164
func (b *bmap) overflow(t *maptype) *bmap {
153165
return *(**bmap)(add(unsafe.Pointer(b), uintptr(t.bucketsize)-regSize))
154166
}
155-
func (b *bmap) setoverflow(t *maptype, ovf *bmap) {
167+
168+
func (h *hmap) setoverflow(t *maptype, b, ovf *bmap) {
169+
if t.bucket.kind&kindNoPointers != 0 {
170+
h.createOverflow()
171+
*h.overflow[0] = append(*h.overflow[0], ovf)
172+
}
156173
*(**bmap)(add(unsafe.Pointer(b), uintptr(t.bucketsize)-regSize)) = ovf
157174
}
158175

176+
func (h *hmap) createOverflow() {
177+
if h.overflow == nil {
178+
h.overflow = new([2]*[]*bmap)
179+
}
180+
if h.overflow[0] == nil {
181+
h.overflow[0] = new([]*bmap)
182+
}
183+
}
184+
159185
func makemap(t *maptype, hint int64) *hmap {
160186
if sz := unsafe.Sizeof(hmap{}); sz > 48 || sz != uintptr(t.hmap.size) {
161187
throw("bad hmap size")
@@ -463,7 +489,7 @@ again:
463489
memstats.next_gc = memstats.heap_alloc
464490
}
465491
newb := (*bmap)(newobject(t.bucket))
466-
b.setoverflow(t, newb)
492+
h.setoverflow(t, b, newb)
467493
inserti = &newb.tophash[0]
468494
insertk = add(unsafe.Pointer(newb), dataOffset)
469495
insertv = add(insertk, bucketCnt*uintptr(t.keysize))
@@ -548,6 +574,8 @@ func mapiterinit(t *maptype, h *hmap, it *hiter) {
548574
it.h = nil
549575
it.buckets = nil
550576
it.bptr = nil
577+
it.overflow[0] = nil
578+
it.overflow[1] = nil
551579

552580
if raceenabled && h != nil {
553581
callerpc := getcallerpc(unsafe.Pointer(&t))
@@ -560,7 +588,7 @@ func mapiterinit(t *maptype, h *hmap, it *hiter) {
560588
return
561589
}
562590

563-
if unsafe.Sizeof(hiter{})/ptrSize != 10 {
591+
if unsafe.Sizeof(hiter{})/ptrSize != 12 {
564592
throw("hash_iter size incorrect") // see ../../cmd/gc/reflect.c
565593
}
566594
it.t = t
@@ -569,6 +597,14 @@ func mapiterinit(t *maptype, h *hmap, it *hiter) {
569597
// grab snapshot of bucket state
570598
it.B = h.B
571599
it.buckets = h.buckets
600+
if t.bucket.kind&kindNoPointers != 0 {
601+
// Allocate the current slice and remember pointers to both current and old.
602+
// This preserves all relevant overflow buckets alive even if
603+
// the table grows and/or overflow buckets are added to the table
604+
// while we are iterating.
605+
h.createOverflow()
606+
it.overflow = *h.overflow
607+
}
572608

573609
// decide where to start
574610
r := uintptr(fastrand1())
@@ -585,14 +621,8 @@ func mapiterinit(t *maptype, h *hmap, it *hiter) {
585621

586622
// Remember we have an iterator.
587623
// Can run concurrently with another hash_iter_init().
588-
for {
589-
old := h.flags
590-
if old == old|iterator|oldIterator {
591-
break
592-
}
593-
if cas(&h.flags, old, old|iterator|oldIterator) {
594-
break
595-
}
624+
if old := h.flags; old&(iterator|oldIterator) != iterator|oldIterator {
625+
atomicor8(&h.flags, iterator|oldIterator)
596626
}
597627

598628
mapiternext(it)
@@ -753,6 +783,15 @@ func hashGrow(t *maptype, h *hmap) {
753783
h.buckets = newbuckets
754784
h.nevacuate = 0
755785

786+
if h.overflow != nil {
787+
// Promote current overflow buckets to the old generation.
788+
if h.overflow[1] != nil {
789+
throw("overflow is not nil")
790+
}
791+
h.overflow[1] = h.overflow[0]
792+
h.overflow[0] = nil
793+
}
794+
756795
// the actual copying of the hash table data is done incrementally
757796
// by growWork() and evacuate().
758797
}
@@ -836,7 +875,7 @@ func evacuate(t *maptype, h *hmap, oldbucket uintptr) {
836875
memstats.next_gc = memstats.heap_alloc
837876
}
838877
newx := (*bmap)(newobject(t.bucket))
839-
x.setoverflow(t, newx)
878+
h.setoverflow(t, x, newx)
840879
x = newx
841880
xi = 0
842881
xk = add(unsafe.Pointer(x), dataOffset)
@@ -863,7 +902,7 @@ func evacuate(t *maptype, h *hmap, oldbucket uintptr) {
863902
memstats.next_gc = memstats.heap_alloc
864903
}
865904
newy := (*bmap)(newobject(t.bucket))
866-
y.setoverflow(t, newy)
905+
h.setoverflow(t, y, newy)
867906
y = newy
868907
yi = 0
869908
yk = add(unsafe.Pointer(y), dataOffset)
@@ -899,6 +938,12 @@ func evacuate(t *maptype, h *hmap, oldbucket uintptr) {
899938
if oldbucket+1 == newbit { // newbit == # of oldbuckets
900939
// Growing is all done. Free old main bucket array.
901940
h.oldbuckets = nil
941+
// Can discard old overflow buckets as well.
942+
// If they are still referenced by an iterator,
943+
// then the iterator holds a pointers to the slice.
944+
if h.overflow != nil {
945+
h.overflow[1] = nil
946+
}
902947
}
903948
}
904949
}

0 commit comments

Comments
 (0)