]> Cypherpunks repositories - gostls13.git/commitdiff
runtime: allocate maps' first bucket table lazily
authorBrad Fitzpatrick <bradfitz@golang.org>
Wed, 27 Mar 2013 23:28:51 +0000 (16:28 -0700)
committerBrad Fitzpatrick <bradfitz@golang.org>
Wed, 27 Mar 2013 23:28:51 +0000 (16:28 -0700)
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
src/pkg/runtime/map_test.go

index 20087cf49533c0002f0412199e892b2cd576d482..6cd5c480d5fadfc5dc5b06492fa627d46091d4a2 100644 (file)
@@ -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;
 
index 29e19db2c6b28606a330d007aae4dabf010939d9..1bf6b60d83e3f63762eba70226ff870c2d268956 100644 (file)
@@ -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)
+       }
+}