mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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 <khr@golang.org>
This commit is contained in:
parent
561ce92fa0
commit
85e7bee19f
3 changed files with 110 additions and 35 deletions
|
|
@ -181,6 +181,10 @@ mapbucket(Type *t)
|
||||||
valuesfield->down = overflowfield;
|
valuesfield->down = overflowfield;
|
||||||
overflowfield->down = T;
|
overflowfield->down = T;
|
||||||
|
|
||||||
|
// See comment on hmap.overflow in ../../runtime/hashmap.go.
|
||||||
|
if(!haspointers(t->type) && !haspointers(t->down))
|
||||||
|
bucket->haspointers = 1; // no pointers
|
||||||
|
|
||||||
bucket->width = offset;
|
bucket->width = offset;
|
||||||
bucket->local = t->local;
|
bucket->local = t->local;
|
||||||
t->bucket = bucket;
|
t->bucket = bucket;
|
||||||
|
|
@ -197,7 +201,7 @@ static Type*
|
||||||
hmap(Type *t)
|
hmap(Type *t)
|
||||||
{
|
{
|
||||||
Type *h, *bucket;
|
Type *h, *bucket;
|
||||||
Type *bucketsfield, *oldbucketsfield;
|
Type *bucketsfield, *oldbucketsfield, *overflowfield;
|
||||||
int32 offset;
|
int32 offset;
|
||||||
|
|
||||||
if(t->hmap != T)
|
if(t->hmap != T)
|
||||||
|
|
@ -208,9 +212,10 @@ hmap(Type *t)
|
||||||
h->noalg = 1;
|
h->noalg = 1;
|
||||||
|
|
||||||
offset = widthint; // count
|
offset = widthint; // count
|
||||||
offset += 4; // flags
|
offset += 1; // flags
|
||||||
offset += 4; // hash0
|
|
||||||
offset += 1; // B
|
offset += 1; // B
|
||||||
|
offset += 2; // padding
|
||||||
|
offset += 4; // hash0
|
||||||
offset = (offset + widthptr - 1) / widthptr * widthptr;
|
offset = (offset + widthptr - 1) / widthptr * widthptr;
|
||||||
|
|
||||||
bucketsfield = typ(TFIELD);
|
bucketsfield = typ(TFIELD);
|
||||||
|
|
@ -227,12 +232,20 @@ hmap(Type *t)
|
||||||
oldbucketsfield->sym->name = "oldbuckets";
|
oldbucketsfield->sym->name = "oldbuckets";
|
||||||
offset += widthptr;
|
offset += widthptr;
|
||||||
|
|
||||||
offset += widthptr; // nevacuate (last field in Hmap)
|
offset += widthptr; // nevacuate
|
||||||
|
|
||||||
|
overflowfield = typ(TFIELD);
|
||||||
|
overflowfield->type = types[TUNSAFEPTR];
|
||||||
|
overflowfield->width = offset;
|
||||||
|
overflowfield->sym = mal(sizeof(Sym));
|
||||||
|
overflowfield->sym->name = "overflow";
|
||||||
|
offset += widthptr;
|
||||||
|
|
||||||
// link up fields
|
// link up fields
|
||||||
h->type = bucketsfield;
|
h->type = bucketsfield;
|
||||||
bucketsfield->down = oldbucketsfield;
|
bucketsfield->down = oldbucketsfield;
|
||||||
oldbucketsfield->down = T;
|
oldbucketsfield->down = overflowfield;
|
||||||
|
overflowfield->down = T;
|
||||||
|
|
||||||
h->width = offset;
|
h->width = offset;
|
||||||
h->local = t->local;
|
h->local = t->local;
|
||||||
|
|
@ -245,7 +258,7 @@ Type*
|
||||||
hiter(Type *t)
|
hiter(Type *t)
|
||||||
{
|
{
|
||||||
int32 n, off;
|
int32 n, off;
|
||||||
Type *field[7];
|
Type *field[9];
|
||||||
Type *i;
|
Type *i;
|
||||||
|
|
||||||
if(t->hiter != T)
|
if(t->hiter != T)
|
||||||
|
|
@ -259,6 +272,7 @@ hiter(Type *t)
|
||||||
// h *Hmap
|
// h *Hmap
|
||||||
// buckets *Bucket
|
// buckets *Bucket
|
||||||
// bptr *Bucket
|
// bptr *Bucket
|
||||||
|
// overflow unsafe.Pointer
|
||||||
// other [4]uintptr
|
// other [4]uintptr
|
||||||
// }
|
// }
|
||||||
// must match ../../runtime/hashmap.c:hash_iter.
|
// must match ../../runtime/hashmap.c:hash_iter.
|
||||||
|
|
@ -292,29 +306,39 @@ hiter(Type *t)
|
||||||
field[5]->sym = mal(sizeof(Sym));
|
field[5]->sym = mal(sizeof(Sym));
|
||||||
field[5]->sym->name = "bptr";
|
field[5]->sym->name = "bptr";
|
||||||
|
|
||||||
// all other non-pointer fields
|
|
||||||
field[6] = typ(TFIELD);
|
field[6] = typ(TFIELD);
|
||||||
field[6]->type = typ(TARRAY);
|
field[6]->type = types[TUNSAFEPTR];
|
||||||
field[6]->type->type = types[TUINTPTR];
|
|
||||||
field[6]->type->bound = 4;
|
|
||||||
field[6]->type->width = 4 * widthptr;
|
|
||||||
field[6]->sym = mal(sizeof(Sym));
|
field[6]->sym = mal(sizeof(Sym));
|
||||||
field[6]->sym->name = "other";
|
field[6]->sym->name = "overflow0";
|
||||||
|
|
||||||
|
field[7] = typ(TFIELD);
|
||||||
|
field[7]->type = types[TUNSAFEPTR];
|
||||||
|
field[7]->sym = mal(sizeof(Sym));
|
||||||
|
field[7]->sym->name = "overflow1";
|
||||||
|
|
||||||
|
// all other non-pointer fields
|
||||||
|
field[8] = typ(TFIELD);
|
||||||
|
field[8]->type = typ(TARRAY);
|
||||||
|
field[8]->type->type = types[TUINTPTR];
|
||||||
|
field[8]->type->bound = 4;
|
||||||
|
field[8]->type->width = 4 * widthptr;
|
||||||
|
field[8]->sym = mal(sizeof(Sym));
|
||||||
|
field[8]->sym->name = "other";
|
||||||
|
|
||||||
// build iterator struct holding the above fields
|
// build iterator struct holding the above fields
|
||||||
i = typ(TSTRUCT);
|
i = typ(TSTRUCT);
|
||||||
i->noalg = 1;
|
i->noalg = 1;
|
||||||
i->type = field[0];
|
i->type = field[0];
|
||||||
off = 0;
|
off = 0;
|
||||||
for(n = 0; n < 6; n++) {
|
for(n = 0; n < nelem(field)-1; n++) {
|
||||||
field[n]->down = field[n+1];
|
field[n]->down = field[n+1];
|
||||||
field[n]->width = off;
|
field[n]->width = off;
|
||||||
off += field[n]->type->width;
|
off += field[n]->type->width;
|
||||||
}
|
}
|
||||||
field[6]->down = T;
|
field[nelem(field)-1]->down = T;
|
||||||
off += field[6]->type->width;
|
off += field[nelem(field)-1]->type->width;
|
||||||
if(off != 10 * widthptr)
|
if(off != 12 * widthptr)
|
||||||
yyerror("hash_iter size not correct %d %d", off, 10 * widthptr);
|
yyerror("hash_iter size not correct %d %d", off, 11 * widthptr);
|
||||||
t->hiter = i;
|
t->hiter = i;
|
||||||
i->map = t;
|
i->map = t;
|
||||||
return i;
|
return i;
|
||||||
|
|
|
||||||
|
|
@ -1469,9 +1469,8 @@ func MapOf(key, elem Type) Type {
|
||||||
|
|
||||||
// Make a map type.
|
// Make a map type.
|
||||||
var imap interface{} = (map[unsafe.Pointer]unsafe.Pointer)(nil)
|
var imap interface{} = (map[unsafe.Pointer]unsafe.Pointer)(nil)
|
||||||
prototype := *(**mapType)(unsafe.Pointer(&imap))
|
|
||||||
mt := new(mapType)
|
mt := new(mapType)
|
||||||
*mt = *prototype
|
*mt = **(**mapType)(unsafe.Pointer(&imap))
|
||||||
mt.string = &s
|
mt.string = &s
|
||||||
mt.hash = fnv1(etyp.hash, 'm', byte(ktyp.hash>>24), byte(ktyp.hash>>16), byte(ktyp.hash>>8), byte(ktyp.hash))
|
mt.hash = fnv1(etyp.hash, 'm', byte(ktyp.hash>>24), byte(ktyp.hash>>16), byte(ktyp.hash>>8), byte(ktyp.hash))
|
||||||
mt.key = ktyp
|
mt.key = ktyp
|
||||||
|
|
@ -1650,6 +1649,12 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func bucketOf(ktyp, etyp *rtype) *rtype {
|
func bucketOf(ktyp, etyp *rtype) *rtype {
|
||||||
|
// See comment on hmap.overflow in ../runtime/hashmap.go.
|
||||||
|
var kind uint8
|
||||||
|
if ktyp.kind&kindNoPointers != 0 && etyp.kind&kindNoPointers != 0 {
|
||||||
|
kind = kindNoPointers
|
||||||
|
}
|
||||||
|
|
||||||
if ktyp.size > maxKeySize {
|
if ktyp.size > maxKeySize {
|
||||||
ktyp = PtrTo(ktyp).(*rtype)
|
ktyp = PtrTo(ktyp).(*rtype)
|
||||||
}
|
}
|
||||||
|
|
@ -1679,6 +1684,7 @@ func bucketOf(ktyp, etyp *rtype) *rtype {
|
||||||
|
|
||||||
b := new(rtype)
|
b := new(rtype)
|
||||||
b.size = gc.size
|
b.size = gc.size
|
||||||
|
b.kind = kind
|
||||||
b.gc[0], _ = gc.finalize()
|
b.gc[0], _ = gc.finalize()
|
||||||
s := "bucket(" + *ktyp.string + "," + *etyp.string + ")"
|
s := "bucket(" + *ktyp.string + "," + *etyp.string + ")"
|
||||||
b.string = &s
|
b.string = &s
|
||||||
|
|
|
||||||
|
|
@ -106,13 +106,24 @@ type hmap struct {
|
||||||
// Note: the format of the Hmap is encoded in ../../cmd/gc/reflect.c and
|
// Note: the format of the Hmap is encoded in ../../cmd/gc/reflect.c and
|
||||||
// ../reflect/type.go. Don't change this structure without also changing that code!
|
// ../reflect/type.go. Don't change this structure without also changing that code!
|
||||||
count int // # live cells == size of map. Must be first (used by len() builtin)
|
count int // # live cells == size of map. Must be first (used by len() builtin)
|
||||||
flags uint32
|
flags uint8
|
||||||
hash0 uint32 // hash seed
|
|
||||||
B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
|
B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
|
||||||
|
hash0 uint32 // hash seed
|
||||||
|
|
||||||
buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
|
buckets unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
|
||||||
oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
|
oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
|
||||||
nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
|
nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
|
||||||
|
|
||||||
|
// If both key and value do not contain pointers, then we mark bucket
|
||||||
|
// type as containing no pointers. This avoids scanning such maps.
|
||||||
|
// However, bmap.overflow is a pointer. In order to keep overflow buckets
|
||||||
|
// alive, we store pointers to all overflow buckets in hmap.overflow.
|
||||||
|
// Overflow is used only if key and value do not contain pointers.
|
||||||
|
// overflow[0] contains overflow buckets for hmap.buckets.
|
||||||
|
// overflow[1] contains overflow buckets for hmap.oldbuckets.
|
||||||
|
// The first indirection allows us to reduce static size of hmap.
|
||||||
|
// The second indirection allows to store a pointer to the slice in hiter.
|
||||||
|
overflow *[2]*[]*bmap
|
||||||
}
|
}
|
||||||
|
|
||||||
// A bucket for a Go map.
|
// A bucket for a Go map.
|
||||||
|
|
@ -135,6 +146,7 @@ type hiter struct {
|
||||||
h *hmap
|
h *hmap
|
||||||
buckets unsafe.Pointer // bucket ptr at hash_iter initialization time
|
buckets unsafe.Pointer // bucket ptr at hash_iter initialization time
|
||||||
bptr *bmap // current bucket
|
bptr *bmap // current bucket
|
||||||
|
overflow [2]*[]*bmap // keeps overflow buckets alive
|
||||||
startBucket uintptr // bucket iteration started at
|
startBucket uintptr // bucket iteration started at
|
||||||
offset uint8 // intra-bucket offset to start from during iteration (should be big enough to hold bucketCnt-1)
|
offset uint8 // intra-bucket offset to start from during iteration (should be big enough to hold bucketCnt-1)
|
||||||
wrapped bool // already wrapped around from end of bucket array to beginning
|
wrapped bool // already wrapped around from end of bucket array to beginning
|
||||||
|
|
@ -152,10 +164,24 @@ func evacuated(b *bmap) bool {
|
||||||
func (b *bmap) overflow(t *maptype) *bmap {
|
func (b *bmap) overflow(t *maptype) *bmap {
|
||||||
return *(**bmap)(add(unsafe.Pointer(b), uintptr(t.bucketsize)-regSize))
|
return *(**bmap)(add(unsafe.Pointer(b), uintptr(t.bucketsize)-regSize))
|
||||||
}
|
}
|
||||||
func (b *bmap) setoverflow(t *maptype, ovf *bmap) {
|
|
||||||
|
func (h *hmap) setoverflow(t *maptype, b, ovf *bmap) {
|
||||||
|
if t.bucket.kind&kindNoPointers != 0 {
|
||||||
|
h.createOverflow()
|
||||||
|
*h.overflow[0] = append(*h.overflow[0], ovf)
|
||||||
|
}
|
||||||
*(**bmap)(add(unsafe.Pointer(b), uintptr(t.bucketsize)-regSize)) = ovf
|
*(**bmap)(add(unsafe.Pointer(b), uintptr(t.bucketsize)-regSize)) = ovf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *hmap) createOverflow() {
|
||||||
|
if h.overflow == nil {
|
||||||
|
h.overflow = new([2]*[]*bmap)
|
||||||
|
}
|
||||||
|
if h.overflow[0] == nil {
|
||||||
|
h.overflow[0] = new([]*bmap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func makemap(t *maptype, hint int64) *hmap {
|
func makemap(t *maptype, hint int64) *hmap {
|
||||||
if sz := unsafe.Sizeof(hmap{}); sz > 48 || sz != uintptr(t.hmap.size) {
|
if sz := unsafe.Sizeof(hmap{}); sz > 48 || sz != uintptr(t.hmap.size) {
|
||||||
throw("bad hmap size")
|
throw("bad hmap size")
|
||||||
|
|
@ -463,7 +489,7 @@ again:
|
||||||
memstats.next_gc = memstats.heap_alloc
|
memstats.next_gc = memstats.heap_alloc
|
||||||
}
|
}
|
||||||
newb := (*bmap)(newobject(t.bucket))
|
newb := (*bmap)(newobject(t.bucket))
|
||||||
b.setoverflow(t, newb)
|
h.setoverflow(t, b, newb)
|
||||||
inserti = &newb.tophash[0]
|
inserti = &newb.tophash[0]
|
||||||
insertk = add(unsafe.Pointer(newb), dataOffset)
|
insertk = add(unsafe.Pointer(newb), dataOffset)
|
||||||
insertv = add(insertk, bucketCnt*uintptr(t.keysize))
|
insertv = add(insertk, bucketCnt*uintptr(t.keysize))
|
||||||
|
|
@ -548,6 +574,8 @@ func mapiterinit(t *maptype, h *hmap, it *hiter) {
|
||||||
it.h = nil
|
it.h = nil
|
||||||
it.buckets = nil
|
it.buckets = nil
|
||||||
it.bptr = nil
|
it.bptr = nil
|
||||||
|
it.overflow[0] = nil
|
||||||
|
it.overflow[1] = nil
|
||||||
|
|
||||||
if raceenabled && h != nil {
|
if raceenabled && h != nil {
|
||||||
callerpc := getcallerpc(unsafe.Pointer(&t))
|
callerpc := getcallerpc(unsafe.Pointer(&t))
|
||||||
|
|
@ -560,7 +588,7 @@ func mapiterinit(t *maptype, h *hmap, it *hiter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if unsafe.Sizeof(hiter{})/ptrSize != 10 {
|
if unsafe.Sizeof(hiter{})/ptrSize != 12 {
|
||||||
throw("hash_iter size incorrect") // see ../../cmd/gc/reflect.c
|
throw("hash_iter size incorrect") // see ../../cmd/gc/reflect.c
|
||||||
}
|
}
|
||||||
it.t = t
|
it.t = t
|
||||||
|
|
@ -569,6 +597,14 @@ func mapiterinit(t *maptype, h *hmap, it *hiter) {
|
||||||
// grab snapshot of bucket state
|
// grab snapshot of bucket state
|
||||||
it.B = h.B
|
it.B = h.B
|
||||||
it.buckets = h.buckets
|
it.buckets = h.buckets
|
||||||
|
if t.bucket.kind&kindNoPointers != 0 {
|
||||||
|
// Allocate the current slice and remember pointers to both current and old.
|
||||||
|
// This preserves all relevant overflow buckets alive even if
|
||||||
|
// the table grows and/or overflow buckets are added to the table
|
||||||
|
// while we are iterating.
|
||||||
|
h.createOverflow()
|
||||||
|
it.overflow = *h.overflow
|
||||||
|
}
|
||||||
|
|
||||||
// decide where to start
|
// decide where to start
|
||||||
r := uintptr(fastrand1())
|
r := uintptr(fastrand1())
|
||||||
|
|
@ -585,14 +621,8 @@ func mapiterinit(t *maptype, h *hmap, it *hiter) {
|
||||||
|
|
||||||
// Remember we have an iterator.
|
// Remember we have an iterator.
|
||||||
// Can run concurrently with another hash_iter_init().
|
// Can run concurrently with another hash_iter_init().
|
||||||
for {
|
if old := h.flags; old&(iterator|oldIterator) != iterator|oldIterator {
|
||||||
old := h.flags
|
atomicor8(&h.flags, iterator|oldIterator)
|
||||||
if old == old|iterator|oldIterator {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if cas(&h.flags, old, old|iterator|oldIterator) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mapiternext(it)
|
mapiternext(it)
|
||||||
|
|
@ -753,6 +783,15 @@ func hashGrow(t *maptype, h *hmap) {
|
||||||
h.buckets = newbuckets
|
h.buckets = newbuckets
|
||||||
h.nevacuate = 0
|
h.nevacuate = 0
|
||||||
|
|
||||||
|
if h.overflow != nil {
|
||||||
|
// Promote current overflow buckets to the old generation.
|
||||||
|
if h.overflow[1] != nil {
|
||||||
|
throw("overflow is not nil")
|
||||||
|
}
|
||||||
|
h.overflow[1] = h.overflow[0]
|
||||||
|
h.overflow[0] = nil
|
||||||
|
}
|
||||||
|
|
||||||
// the actual copying of the hash table data is done incrementally
|
// the actual copying of the hash table data is done incrementally
|
||||||
// by growWork() and evacuate().
|
// by growWork() and evacuate().
|
||||||
}
|
}
|
||||||
|
|
@ -836,7 +875,7 @@ func evacuate(t *maptype, h *hmap, oldbucket uintptr) {
|
||||||
memstats.next_gc = memstats.heap_alloc
|
memstats.next_gc = memstats.heap_alloc
|
||||||
}
|
}
|
||||||
newx := (*bmap)(newobject(t.bucket))
|
newx := (*bmap)(newobject(t.bucket))
|
||||||
x.setoverflow(t, newx)
|
h.setoverflow(t, x, newx)
|
||||||
x = newx
|
x = newx
|
||||||
xi = 0
|
xi = 0
|
||||||
xk = add(unsafe.Pointer(x), dataOffset)
|
xk = add(unsafe.Pointer(x), dataOffset)
|
||||||
|
|
@ -863,7 +902,7 @@ func evacuate(t *maptype, h *hmap, oldbucket uintptr) {
|
||||||
memstats.next_gc = memstats.heap_alloc
|
memstats.next_gc = memstats.heap_alloc
|
||||||
}
|
}
|
||||||
newy := (*bmap)(newobject(t.bucket))
|
newy := (*bmap)(newobject(t.bucket))
|
||||||
y.setoverflow(t, newy)
|
h.setoverflow(t, y, newy)
|
||||||
y = newy
|
y = newy
|
||||||
yi = 0
|
yi = 0
|
||||||
yk = add(unsafe.Pointer(y), dataOffset)
|
yk = add(unsafe.Pointer(y), dataOffset)
|
||||||
|
|
@ -899,6 +938,12 @@ func evacuate(t *maptype, h *hmap, oldbucket uintptr) {
|
||||||
if oldbucket+1 == newbit { // newbit == # of oldbuckets
|
if oldbucket+1 == newbit { // newbit == # of oldbuckets
|
||||||
// Growing is all done. Free old main bucket array.
|
// Growing is all done. Free old main bucket array.
|
||||||
h.oldbuckets = nil
|
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.overflow != nil {
|
||||||
|
h.overflow[1] = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue