From 5b3ff61be63c87ff3e85609c774143b63e762f4b Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 27 Mar 2013 16:28:51 -0700 Subject: [PATCH] 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 --- src/pkg/runtime/hashmap.c | 31 ++++++++++++++++++++++++++----- src/pkg/runtime/map_test.go | 7 +++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/pkg/runtime/hashmap.c b/src/pkg/runtime/hashmap.c index 20087cf495..6cd5c480d5 100644 --- a/src/pkg/runtime/hashmap.c +++ b/src/pkg/runtime/hashmap.c @@ -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; diff --git a/src/pkg/runtime/map_test.go b/src/pkg/runtime/map_test.go index 29e19db2c6..1bf6b60d83 100644 --- a/src/pkg/runtime/map_test.go +++ b/src/pkg/runtime/map_test.go @@ -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) + } +}