1
0
mirror of https://github.com/golang/go synced 2024-11-20 01:14:40 -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:
Brad Fitzpatrick 2013-03-27 16:28:51 -07:00
parent b735eeb323
commit 5b3ff61be6
2 changed files with 33 additions and 5 deletions

View File

@ -255,10 +255,15 @@ hash_init(MapType *t, Hmap *h, uint32 hint)
// allocate initial hash table // allocate initial hash table
// If hint is large zeroing this memory could take a while. // If hint is large zeroing this memory could take a while.
if(checkgc) mstats.next_gc = mstats.heap_alloc; if(checkgc) mstats.next_gc = mstats.heap_alloc;
buckets = runtime·mallocgc(bucketsize << B, 0, 1, 0); if(B == 0) {
for(i = 0; i < (uintptr)1 << B; i++) { // done lazily later.
b = (Bucket*)(buckets + i * bucketsize); buckets = nil;
clearbucket(b); } 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 // initialize Hmap
@ -485,6 +490,8 @@ hash_lookup(MapType *t, Hmap *h, byte **keyp)
key = *keyp; key = *keyp;
if(docheck) if(docheck)
check(t, h); check(t, h);
if(h->count == 0)
return nil;
hash = h->hash0; hash = h->hash0;
t->key->alg->hash(&hash, t->key->size, key); t->key->alg->hash(&hash, t->key->size, key);
bucket = hash & (((uintptr)1 << h->B) - 1); bucket = hash & (((uintptr)1 << h->B) - 1);
@ -572,6 +579,12 @@ hash_insert(MapType *t, Hmap *h, void *key, void *value)
check(t, h); check(t, h);
hash = h->hash0; hash = h->hash0;
t->key->alg->hash(&hash, t->key->size, key); 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: again:
bucket = hash & (((uintptr)1 << h->B) - 1); bucket = hash & (((uintptr)1 << h->B) - 1);
if(h->oldbuckets != nil) if(h->oldbuckets != nil)
@ -659,6 +672,8 @@ hash_remove(MapType *t, Hmap *h, void *key)
if(docheck) if(docheck)
check(t, h); check(t, h);
if(h->count == 0)
return;
hash = h->hash0; hash = h->hash0;
t->key->alg->hash(&hash, t->key->size, key); t->key->alg->hash(&hash, t->key->size, key);
bucket = hash & (((uintptr)1 << h->B) - 1); 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. // Remember we have an iterator at this level.
h->flags |= Iterator; 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 // initializes it->key and it->value to the next key/value pair
@ -848,7 +869,7 @@ next:
bool bool
hash_gciter_init (Hmap *h, struct hash_gciter *it) 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) if(h->buckets == nil)
return false; return false;

View File

@ -280,3 +280,10 @@ func TestEmptyKeyAndValue(t *testing.T) {
t.Errorf("empty key returned wrong value") 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)
}
}