mirror of
https://github.com/golang/go
synced 2024-11-26 02:47:58 -07:00
runtime: allocate maps' first bucket table lazily
Motivated by garbage profiling in HTTP benchmarks. This changes means new empty maps are just one small allocation (the HMap) instead the HMap + the relatively larger h->buckets allocation. This helps maps which remain empty throughout their life. benchmark old ns/op new ns/op delta BenchmarkNewEmptyMap 196 107 -45.41% benchmark old allocs new allocs delta BenchmarkNewEmptyMap 2 1 -50.00% benchmark old bytes new bytes delta BenchmarkNewEmptyMap 195 50 -74.36% R=khr, golang-dev, r CC=golang-dev https://golang.org/cl/7722046
This commit is contained in:
parent
b735eeb323
commit
5b3ff61be6
@ -255,10 +255,15 @@ hash_init(MapType *t, Hmap *h, uint32 hint)
|
||||
// allocate initial hash table
|
||||
// If hint is large zeroing this memory could take a while.
|
||||
if(checkgc) mstats.next_gc = mstats.heap_alloc;
|
||||
buckets = runtime·mallocgc(bucketsize << B, 0, 1, 0);
|
||||
for(i = 0; i < (uintptr)1 << B; i++) {
|
||||
b = (Bucket*)(buckets + i * bucketsize);
|
||||
clearbucket(b);
|
||||
if(B == 0) {
|
||||
// done lazily later.
|
||||
buckets = nil;
|
||||
} else {
|
||||
buckets = runtime·mallocgc(bucketsize << B, 0, 1, 0);
|
||||
for(i = 0; i < (uintptr)1 << B; i++) {
|
||||
b = (Bucket*)(buckets + i * bucketsize);
|
||||
clearbucket(b);
|
||||
}
|
||||
}
|
||||
|
||||
// initialize Hmap
|
||||
@ -485,6 +490,8 @@ hash_lookup(MapType *t, Hmap *h, byte **keyp)
|
||||
key = *keyp;
|
||||
if(docheck)
|
||||
check(t, h);
|
||||
if(h->count == 0)
|
||||
return nil;
|
||||
hash = h->hash0;
|
||||
t->key->alg->hash(&hash, t->key->size, key);
|
||||
bucket = hash & (((uintptr)1 << h->B) - 1);
|
||||
@ -572,6 +579,12 @@ hash_insert(MapType *t, Hmap *h, void *key, void *value)
|
||||
check(t, h);
|
||||
hash = h->hash0;
|
||||
t->key->alg->hash(&hash, t->key->size, key);
|
||||
if(h->buckets == nil) {
|
||||
h->buckets = runtime·mallocgc(h->bucketsize, 0, 1, 0);
|
||||
b = (Bucket*)(h->buckets);
|
||||
clearbucket(b);
|
||||
}
|
||||
|
||||
again:
|
||||
bucket = hash & (((uintptr)1 << h->B) - 1);
|
||||
if(h->oldbuckets != nil)
|
||||
@ -659,6 +672,8 @@ hash_remove(MapType *t, Hmap *h, void *key)
|
||||
|
||||
if(docheck)
|
||||
check(t, h);
|
||||
if(h->count == 0)
|
||||
return;
|
||||
hash = h->hash0;
|
||||
t->key->alg->hash(&hash, t->key->size, key);
|
||||
bucket = hash & (((uintptr)1 << h->B) - 1);
|
||||
@ -749,6 +764,12 @@ hash_iter_init(MapType *t, Hmap *h, struct hash_iter *it)
|
||||
|
||||
// Remember we have an iterator at this level.
|
||||
h->flags |= Iterator;
|
||||
|
||||
if(h->buckets == nil) {
|
||||
// Empty map. Force next hash_next to exit without
|
||||
// evalulating h->bucket.
|
||||
it->wrapped = true;
|
||||
}
|
||||
}
|
||||
|
||||
// initializes it->key and it->value to the next key/value pair
|
||||
@ -848,7 +869,7 @@ next:
|
||||
bool
|
||||
hash_gciter_init (Hmap *h, struct hash_gciter *it)
|
||||
{
|
||||
// GC during map initialization
|
||||
// GC during map initialization or on an empty map.
|
||||
if(h->buckets == nil)
|
||||
return false;
|
||||
|
||||
|
@ -280,3 +280,10 @@ func TestEmptyKeyAndValue(t *testing.T) {
|
||||
t.Errorf("empty key returned wrong value")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewEmptyMap(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = make(map[int]int)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user