Commit 3be4d957 authored by Josh Bleecher Snyder's avatar Josh Bleecher Snyder Committed by Keith Randall

runtime: change map iteration randomization to use intra-bucket offset

Map iteration previously started from a random bucket, but walked each
bucket from the beginning. Now, iteration always starts from the first
bucket and walks each bucket starting at a random offset. For
performance, the random offset is selected at the start of iteration
and reused for each bucket.

Iteration over a map with 8 or fewer elements--a single bucket--will
now be non-deterministic. There will now be only 8 different possible
map iterations.

Significant benchmark changes, on my OS X laptop (rough but consistent):

benchmark                              old ns/op     new ns/op     delta
BenchmarkMapIter                       128           121           -5.47%
BenchmarkMapIterEmpty                  4.26          4.45          +4.46%
BenchmarkNewEmptyMap                   114           111           -2.63%

Fixes #6719.

R=khr, bradfitz
CC=golang-codereviews
https://golang.org/cl/47370043
parent 1e2b1335
...@@ -252,7 +252,7 @@ hiter(Type *t) ...@@ -252,7 +252,7 @@ hiter(Type *t)
// h *Hmap // h *Hmap
// buckets *Bucket // buckets *Bucket
// bptr *Bucket // bptr *Bucket
// other [5]uintptr // other [4]uintptr
// } // }
// must match ../../pkg/runtime/hashmap.c:hash_iter. // must match ../../pkg/runtime/hashmap.c:hash_iter.
field[0] = typ(TFIELD); field[0] = typ(TFIELD);
...@@ -289,8 +289,8 @@ hiter(Type *t) ...@@ -289,8 +289,8 @@ hiter(Type *t)
field[6] = typ(TFIELD); field[6] = typ(TFIELD);
field[6]->type = typ(TARRAY); field[6]->type = typ(TARRAY);
field[6]->type->type = types[TUINTPTR]; field[6]->type->type = types[TUINTPTR];
field[6]->type->bound = 5; field[6]->type->bound = 4;
field[6]->type->width = 5 * widthptr; field[6]->type->width = 4 * widthptr;
field[6]->sym = mal(sizeof(Sym)); field[6]->sym = mal(sizeof(Sym));
field[6]->sym->name = "other"; field[6]->sym->name = "other";
...@@ -306,8 +306,8 @@ hiter(Type *t) ...@@ -306,8 +306,8 @@ hiter(Type *t)
} }
field[6]->down = T; field[6]->down = T;
off += field[6]->type->width; off += field[6]->type->width;
if(off != 11 * widthptr) if(off != 10 * widthptr)
yyerror("hash_iter size not correct %d %d", off, 11 * widthptr); yyerror("hash_iter size not correct %d %d", off, 10 * widthptr);
t->hiter = i; t->hiter = i;
i->map = t; i->map = t;
return i; return i;
......
...@@ -746,9 +746,8 @@ struct hash_iter ...@@ -746,9 +746,8 @@ struct hash_iter
byte *buckets; // bucket ptr at hash_iter initialization time byte *buckets; // bucket ptr at hash_iter initialization time
struct Bucket *bptr; // current bucket struct Bucket *bptr; // current bucket
// end point for iteration uint32 offset; // intra-bucket offset to start from during iteration
uintptr endbucket; bool done;
bool wrapped;
// state of table at time iterator is initialized // state of table at time iterator is initialized
uint8 B; uint8 B;
...@@ -768,8 +767,8 @@ hash_iter_init(MapType *t, Hmap *h, struct hash_iter *it) ...@@ -768,8 +767,8 @@ hash_iter_init(MapType *t, Hmap *h, struct hash_iter *it)
{ {
uint32 old; uint32 old;
if(sizeof(struct hash_iter) / sizeof(uintptr) != 11) { if(sizeof(struct hash_iter) / sizeof(uintptr) != 10) {
runtime·throw("hash_iter size incorrect"); // see ../../cmd/gc/range.c runtime·throw("hash_iter size incorrect"); // see ../../cmd/gc/reflect.c
} }
it->t = t; it->t = t;
it->h = h; it->h = h;
...@@ -779,8 +778,9 @@ hash_iter_init(MapType *t, Hmap *h, struct hash_iter *it) ...@@ -779,8 +778,9 @@ hash_iter_init(MapType *t, Hmap *h, struct hash_iter *it)
it->buckets = h->buckets; it->buckets = h->buckets;
// iterator state // iterator state
it->bucket = it->endbucket = runtime·fastrand1() & (((uintptr)1 << h->B) - 1); it->bucket = 0;
it->wrapped = false; it->offset = runtime·fastrand1() & (BUCKETSIZE - 1);
it->done = false;
it->bptr = nil; it->bptr = nil;
// Remember we have an iterator. // Remember we have an iterator.
...@@ -795,8 +795,8 @@ hash_iter_init(MapType *t, Hmap *h, struct hash_iter *it) ...@@ -795,8 +795,8 @@ hash_iter_init(MapType *t, Hmap *h, struct hash_iter *it)
if(h->buckets == nil) { if(h->buckets == nil) {
// Empty map. Force next hash_next to exit without // Empty map. Force next hash_next to exit without
// evalulating h->bucket. // evaluating h->bucket.
it->wrapped = true; it->done = true;
} }
} }
...@@ -810,7 +810,7 @@ hash_next(struct hash_iter *it) ...@@ -810,7 +810,7 @@ hash_next(struct hash_iter *it)
uintptr bucket, oldbucket; uintptr bucket, oldbucket;
uintptr hash; uintptr hash;
Bucket *b; Bucket *b;
uintptr i; uintptr i, offi;
intptr check_bucket; intptr check_bucket;
bool eq; bool eq;
byte *k, *v; byte *k, *v;
...@@ -825,7 +825,7 @@ hash_next(struct hash_iter *it) ...@@ -825,7 +825,7 @@ hash_next(struct hash_iter *it)
next: next:
if(b == nil) { if(b == nil) {
if(bucket == it->endbucket && it->wrapped) { if(it->done) {
// end of iteration // end of iteration
it->key = nil; it->key = nil;
it->value = nil; it->value = nil;
...@@ -851,14 +851,15 @@ next: ...@@ -851,14 +851,15 @@ next:
bucket++; bucket++;
if(bucket == ((uintptr)1 << it->B)) { if(bucket == ((uintptr)1 << it->B)) {
bucket = 0; bucket = 0;
it->wrapped = true; it->done = true;
} }
i = 0; i = 0;
} }
k = b->data + h->keysize * i; for(; i < BUCKETSIZE; i++) {
v = b->data + h->keysize * BUCKETSIZE + h->valuesize * i; offi = (i + it->offset) & (BUCKETSIZE - 1);
for(; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { k = b->data + h->keysize * offi;
if(b->tophash[i] != Empty && b->tophash[i] != EvacuatedEmpty) { v = b->data + h->keysize * BUCKETSIZE + h->valuesize * offi;
if(b->tophash[offi] != Empty && b->tophash[offi] != EvacuatedEmpty) {
if(check_bucket >= 0) { if(check_bucket >= 0) {
// Special case: iterator was started during a grow and the // Special case: iterator was started during a grow and the
// grow is not done yet. We're working on a bucket whose // grow is not done yet. We're working on a bucket whose
...@@ -884,12 +885,12 @@ next: ...@@ -884,12 +885,12 @@ next:
// NOTE: this case is why we need two evacuate tophash // NOTE: this case is why we need two evacuate tophash
// values, evacuatedX and evacuatedY, that differ in // values, evacuatedX and evacuatedY, that differ in
// their low bit. // their low bit.
if(check_bucket >> (it->B - 1) != (b->tophash[i] & 1)) { if(check_bucket >> (it->B - 1) != (b->tophash[offi] & 1)) {
continue; continue;
} }
} }
} }
if(b->tophash[i] != EvacuatedX && b->tophash[i] != EvacuatedY) { if(b->tophash[offi] != EvacuatedX && b->tophash[offi] != EvacuatedY) {
// this is the golden data, we can return it. // this is the golden data, we can return it.
it->key = IK(h, k); it->key = IK(h, k);
it->value = IV(h, v); it->value = IV(h, v);
......
...@@ -411,8 +411,7 @@ func TestMapNanGrowIterator(t *testing.T) { ...@@ -411,8 +411,7 @@ func TestMapNanGrowIterator(t *testing.T) {
} }
func TestMapIterOrder(t *testing.T) { func TestMapIterOrder(t *testing.T) {
// TODO: For issue 6719, add 3 and 7 to this list. for _, n := range [...]int{3, 7, 9, 15} {
for _, n := range [...]int{9, 15} {
// Make m be {0: true, 1: true, ..., n-1: true}. // Make m be {0: true, 1: true, ..., n-1: true}.
m := make(map[int]bool) m := make(map[int]bool)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment