Anonymous avatar Anonymous committed 6efdf4f

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://codereview.appspot.com/7722046

Comments (0)

Files changed (2)

src/pkg/runtime/hashmap.c

 	// 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
 	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);
 		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)
 	
 	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);
 
 	// 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
 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;
 

src/pkg/runtime/map_test.go

 		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)
+	}
+}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.