Commit 9aae6c1a authored by Keith Randall's avatar Keith Randall

runtime: don't store evacuate bit as low bit of hashtable overflow pointer.

Hash tables currently store an evacuated bit in the low bit
of the overflow pointer.  That's probably not sustainable in the
long term as GC wants correctly typed & aligned pointers.  It is
also a pain to move any of this code to Go in the current state.

This change moves the evacuated bit into the tophash entries.

Performance change is negligable.

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/14412043
parent ab05a855
...@@ -76,7 +76,7 @@ struct Bucket ...@@ -76,7 +76,7 @@ struct Bucket
{ {
// Note: the format of the Bucket is encoded in ../../cmd/gc/reflect.c and // Note: the format of the Bucket is encoded in ../../cmd/gc/reflect.c and
// ../reflect/type.go. Don't change this structure without also changing that code! // ../reflect/type.go. Don't change this structure without also changing that code!
uint8 tophash[BUCKETSIZE]; // top 8 bits of hash of each entry (0 = empty) uint8 tophash[BUCKETSIZE]; // top 8 bits of hash of each entry (or special mark below)
Bucket *overflow; // overflow bucket, if any Bucket *overflow; // overflow bucket, if any
byte data[1]; // BUCKETSIZE keys followed by BUCKETSIZE values byte data[1]; // BUCKETSIZE keys followed by BUCKETSIZE values
}; };
...@@ -84,12 +84,19 @@ struct Bucket ...@@ -84,12 +84,19 @@ struct Bucket
// code a bit more complicated than alternating key/value/key/value/... but it allows // code a bit more complicated than alternating key/value/key/value/... but it allows
// us to eliminate padding which would be needed for, e.g., map[int64]int8. // us to eliminate padding which would be needed for, e.g., map[int64]int8.
// Low-order bit of overflow field is used to mark a bucket as already evacuated // tophash values. We reserve a few possibilities for special marks.
// without destroying the overflow pointer. // Each bucket (including its overflow buckets, if any) will have either all or none of its
// Only buckets in oldbuckets will be marked as evacuated. // entries in the Evacuated* states (except during the evacuate() method, which only happens
// Evacuated bit will be set identically on the base bucket and any overflow buckets. // during map writes and thus no one else can observe the map during that time).
#define evacuated(b) (((uintptr)(b)->overflow & 1) != 0) enum
#define overflowptr(b) ((Bucket*)((uintptr)(b)->overflow & ~(uintptr)1)) {
Empty = 0, // cell is empty
EvacuatedEmpty = 1, // cell is empty, bucket is evacuated.
EvacuatedX = 2, // key/value is valid. Entry has been evacuated to first half of larger table.
EvacuatedY = 3, // same as above, but evacuated to second half of larger table.
MinTopHash = 4, // minimum tophash for a normal filled cell.
};
#define evacuated(b) ((b)->tophash[0] > Empty && (b)->tophash[0] < MinTopHash)
struct Hmap struct Hmap
{ {
...@@ -143,16 +150,12 @@ check(MapType *t, Hmap *h) ...@@ -143,16 +150,12 @@ check(MapType *t, Hmap *h)
// check buckets // check buckets
for(bucket = 0; bucket < (uintptr)1 << h->B; bucket++) { for(bucket = 0; bucket < (uintptr)1 << h->B; bucket++) {
if(h->oldbuckets != nil) {
oldbucket = bucket & (((uintptr)1 << (h->B - 1)) - 1);
b = (Bucket*)(h->oldbuckets + oldbucket * h->bucketsize);
if(!evacuated(b))
continue; // b is still uninitialized
}
for(b = (Bucket*)(h->buckets + bucket * h->bucketsize); b != nil; b = b->overflow) { for(b = (Bucket*)(h->buckets + bucket * h->bucketsize); b != nil; b = b->overflow) {
for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) {
if(b->tophash[i] == 0) if(b->tophash[i] == Empty)
continue; continue;
if(b->tophash[i] > Empty && b->tophash[i] < MinTopHash)
runtime·throw("evacuated cell in buckets");
cnt++; cnt++;
t->key->alg->equal(&eq, t->key->size, IK(h, k), IK(h, k)); t->key->alg->equal(&eq, t->key->size, IK(h, k), IK(h, k));
if(!eq) if(!eq)
...@@ -160,8 +163,8 @@ check(MapType *t, Hmap *h) ...@@ -160,8 +163,8 @@ check(MapType *t, Hmap *h)
hash = h->hash0; hash = h->hash0;
t->key->alg->hash(&hash, t->key->size, IK(h, k)); t->key->alg->hash(&hash, t->key->size, IK(h, k));
top = hash >> (8*sizeof(uintptr) - 8); top = hash >> (8*sizeof(uintptr) - 8);
if(top == 0) if(top < MinTopHash)
top = 1; top += MinTopHash;
if(top != b->tophash[i]) if(top != b->tophash[i])
runtime·throw("bad hash"); runtime·throw("bad hash");
} }
...@@ -172,14 +175,12 @@ check(MapType *t, Hmap *h) ...@@ -172,14 +175,12 @@ check(MapType *t, Hmap *h)
if(h->oldbuckets != nil) { if(h->oldbuckets != nil) {
for(oldbucket = 0; oldbucket < (uintptr)1 << (h->B - 1); oldbucket++) { for(oldbucket = 0; oldbucket < (uintptr)1 << (h->B - 1); oldbucket++) {
b = (Bucket*)(h->oldbuckets + oldbucket * h->bucketsize); b = (Bucket*)(h->oldbuckets + oldbucket * h->bucketsize);
if(evacuated(b)) for(; b != nil; b = b->overflow) {
continue;
if(oldbucket < h->nevacuate)
runtime·throw("bucket became unevacuated");
for(; b != nil; b = overflowptr(b)) {
for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) {
if(b->tophash[i] == 0) if(b->tophash[i] < MinTopHash)
continue; continue;
if(oldbucket < h->nevacuate)
runtime·throw("unevacuated entry in an evacuated bucket");
cnt++; cnt++;
t->key->alg->equal(&eq, t->key->size, IK(h, k), IK(h, k)); t->key->alg->equal(&eq, t->key->size, IK(h, k), IK(h, k));
if(!eq) if(!eq)
...@@ -187,8 +188,8 @@ check(MapType *t, Hmap *h) ...@@ -187,8 +188,8 @@ check(MapType *t, Hmap *h)
hash = h->hash0; hash = h->hash0;
t->key->alg->hash(&hash, t->key->size, IK(h, k)); t->key->alg->hash(&hash, t->key->size, IK(h, k));
top = hash >> (8*sizeof(uintptr) - 8); top = hash >> (8*sizeof(uintptr) - 8);
if(top == 0) if(top < MinTopHash)
top = 1; top += MinTopHash;
if(top != b->tophash[i]) if(top != b->tophash[i])
runtime·throw("bad hash (old)"); runtime·throw("bad hash (old)");
} }
...@@ -273,13 +274,12 @@ hash_init(MapType *t, Hmap *h, uint32 hint) ...@@ -273,13 +274,12 @@ hash_init(MapType *t, Hmap *h, uint32 hint)
} }
// Moves entries in oldbuckets[i] to buckets[i] and buckets[i+2^k]. // Moves entries in oldbuckets[i] to buckets[i] and buckets[i+2^k].
// We leave the original bucket intact, except for the evacuated marks, so that // We leave the original bucket intact, except for marking the topbits
// iterators can still iterate through the old buckets. // entries as evacuated, so that iterators can still iterate through the old buckets.
static void static void
evacuate(MapType *t, Hmap *h, uintptr oldbucket) evacuate(MapType *t, Hmap *h, uintptr oldbucket)
{ {
Bucket *b; Bucket *b;
Bucket *nextb;
Bucket *x, *y; Bucket *x, *y;
Bucket *newx, *newy; Bucket *newx, *newy;
uintptr xi, yi; uintptr xi, yi;
...@@ -306,11 +306,15 @@ evacuate(MapType *t, Hmap *h, uintptr oldbucket) ...@@ -306,11 +306,15 @@ evacuate(MapType *t, Hmap *h, uintptr oldbucket)
yk = y->data; yk = y->data;
xv = xk + h->keysize * BUCKETSIZE; xv = xk + h->keysize * BUCKETSIZE;
yv = yk + h->keysize * BUCKETSIZE; yv = yk + h->keysize * BUCKETSIZE;
do { for(; b != nil; b = b->overflow) {
for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) {
top = b->tophash[i]; top = b->tophash[i];
if(top == 0) if(top == Empty) {
b->tophash[i] = EvacuatedEmpty;
continue; continue;
}
if(top < MinTopHash)
runtime·throw("bad state");
// Compute hash to make our evacuation decision (whether we need // Compute hash to make our evacuation decision (whether we need
// to send this key/value to bucket x or bucket y). // to send this key/value to bucket x or bucket y).
...@@ -335,12 +339,13 @@ evacuate(MapType *t, Hmap *h, uintptr oldbucket) ...@@ -335,12 +339,13 @@ evacuate(MapType *t, Hmap *h, uintptr oldbucket)
else else
hash &= ~newbit; hash &= ~newbit;
top = hash >> (8*sizeof(uintptr)-8); top = hash >> (8*sizeof(uintptr)-8);
if(top == 0) if(top < MinTopHash)
top = 1; top += MinTopHash;
} }
} }
if((hash & newbit) == 0) { if((hash & newbit) == 0) {
b->tophash[i] = EvacuatedX;
if(xi == BUCKETSIZE) { if(xi == BUCKETSIZE) {
if(checkgc) mstats.next_gc = mstats.heap_alloc; if(checkgc) mstats.next_gc = mstats.heap_alloc;
newx = runtime·cnew(t->bucket); newx = runtime·cnew(t->bucket);
...@@ -365,6 +370,7 @@ evacuate(MapType *t, Hmap *h, uintptr oldbucket) ...@@ -365,6 +370,7 @@ evacuate(MapType *t, Hmap *h, uintptr oldbucket)
xk += h->keysize; xk += h->keysize;
xv += h->valuesize; xv += h->valuesize;
} else { } else {
b->tophash[i] = EvacuatedY;
if(yi == BUCKETSIZE) { if(yi == BUCKETSIZE) {
if(checkgc) mstats.next_gc = mstats.heap_alloc; if(checkgc) mstats.next_gc = mstats.heap_alloc;
newy = runtime·cnew(t->bucket); newy = runtime·cnew(t->bucket);
...@@ -390,26 +396,21 @@ evacuate(MapType *t, Hmap *h, uintptr oldbucket) ...@@ -390,26 +396,21 @@ evacuate(MapType *t, Hmap *h, uintptr oldbucket)
yv += h->valuesize; yv += h->valuesize;
} }
} }
}
// mark as evacuated so we don't do it again. // Unlink the overflow buckets & clear key/value to help GC.
// this also tells any iterators that this data isn't golden anymore. if((h->flags & OldIterator) == 0) {
nextb = b->overflow; b = (Bucket*)(h->oldbuckets + oldbucket * h->bucketsize);
b->overflow = (Bucket*)((uintptr)nextb + 1); b->overflow = nil;
runtime·memclr(b->data, h->bucketsize - offsetof(Bucket, data[0]));
b = nextb; }
} while(b != nil);
// Unlink the overflow buckets to help GC.
b = (Bucket*)(h->oldbuckets + oldbucket * h->bucketsize);
if((h->flags & OldIterator) == 0)
b->overflow = (Bucket*)1;
} }
// advance evacuation mark // Advance evacuation mark
if(oldbucket == h->nevacuate) { if(oldbucket == h->nevacuate) {
h->nevacuate = oldbucket + 1; h->nevacuate = oldbucket + 1;
if(oldbucket + 1 == newbit) // newbit == # of oldbuckets if(oldbucket + 1 == newbit) // newbit == # of oldbuckets
// free main bucket array // Growing is all done. Free old main bucket array.
h->oldbuckets = nil; h->oldbuckets = nil;
} }
if(docheck) if(docheck)
...@@ -443,7 +444,6 @@ hash_grow(MapType *t, Hmap *h) ...@@ -443,7 +444,6 @@ hash_grow(MapType *t, Hmap *h)
if(h->oldbuckets != nil) if(h->oldbuckets != nil)
runtime·throw("evacuation not done in time"); runtime·throw("evacuation not done in time");
old_buckets = h->buckets; old_buckets = h->buckets;
// NOTE: this could be a big malloc, but since we don't need zeroing it is probably fast.
if(checkgc) mstats.next_gc = mstats.heap_alloc; if(checkgc) mstats.next_gc = mstats.heap_alloc;
new_buckets = runtime·cnewarray(t->bucket, (uintptr)1 << (h->B + 1)); new_buckets = runtime·cnewarray(t->bucket, (uintptr)1 << (h->B + 1));
flags = (h->flags & ~(Iterator | OldIterator)); flags = (h->flags & ~(Iterator | OldIterator));
...@@ -495,8 +495,8 @@ hash_lookup(MapType *t, Hmap *h, byte **keyp) ...@@ -495,8 +495,8 @@ hash_lookup(MapType *t, Hmap *h, byte **keyp)
b = (Bucket*)(h->buckets + bucket * h->bucketsize); b = (Bucket*)(h->buckets + bucket * h->bucketsize);
} }
top = hash >> (sizeof(uintptr)*8 - 8); top = hash >> (sizeof(uintptr)*8 - 8);
if(top == 0) if(top < MinTopHash)
top = 1; top += MinTopHash;
do { do {
for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) {
if(b->tophash[i] == top) { if(b->tophash[i] == top) {
...@@ -608,15 +608,15 @@ hash_insert(MapType *t, Hmap *h, void *key, void *value) ...@@ -608,15 +608,15 @@ hash_insert(MapType *t, Hmap *h, void *key, void *value)
grow_work(t, h, bucket); grow_work(t, h, bucket);
b = (Bucket*)(h->buckets + bucket * h->bucketsize); b = (Bucket*)(h->buckets + bucket * h->bucketsize);
top = hash >> (sizeof(uintptr)*8 - 8); top = hash >> (sizeof(uintptr)*8 - 8);
if(top == 0) if(top < MinTopHash)
top = 1; top += MinTopHash;
inserti = nil; inserti = nil;
insertk = nil; insertk = nil;
insertv = nil; insertv = nil;
while(true) { while(true) {
for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) {
if(b->tophash[i] != top) { if(b->tophash[i] != top) {
if(b->tophash[i] == 0 && inserti == nil) { if(b->tophash[i] == Empty && inserti == nil) {
inserti = &b->tophash[i]; inserti = &b->tophash[i];
insertk = k; insertk = k;
insertv = v; insertv = v;
...@@ -697,8 +697,8 @@ hash_remove(MapType *t, Hmap *h, void *key) ...@@ -697,8 +697,8 @@ hash_remove(MapType *t, Hmap *h, void *key)
grow_work(t, h, bucket); grow_work(t, h, bucket);
b = (Bucket*)(h->buckets + bucket * h->bucketsize); b = (Bucket*)(h->buckets + bucket * h->bucketsize);
top = hash >> (sizeof(uintptr)*8 - 8); top = hash >> (sizeof(uintptr)*8 - 8);
if(top == 0) if(top < MinTopHash)
top = 1; top += MinTopHash;
do { do {
for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) {
if(b->tophash[i] != top) if(b->tophash[i] != top)
...@@ -718,7 +718,7 @@ hash_remove(MapType *t, Hmap *h, void *key) ...@@ -718,7 +718,7 @@ hash_remove(MapType *t, Hmap *h, void *key)
t->elem->alg->copy(t->elem->size, v, nil); t->elem->alg->copy(t->elem->size, v, nil);
} }
b->tophash[i] = 0; b->tophash[i] = Empty;
h->count--; h->count--;
// TODO: consolidate buckets if they are mostly empty // TODO: consolidate buckets if they are mostly empty
...@@ -857,11 +857,12 @@ next: ...@@ -857,11 +857,12 @@ next:
k = b->data + h->keysize * i; k = b->data + h->keysize * i;
v = b->data + h->keysize * BUCKETSIZE + h->valuesize * i; v = b->data + h->keysize * BUCKETSIZE + h->valuesize * i;
for(; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { for(; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) {
if(b->tophash[i] != 0) { if(b->tophash[i] != Empty && b->tophash[i] != 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
// oldbucket has not been evacuated yet. So we're iterating // oldbucket has not been evacuated yet. Or at least, it wasn't
// evacuated when we started the bucket. So we're iterating
// through the oldbucket, skipping any keys that will go // through the oldbucket, skipping any keys that will go
// to the other new bucket (each oldbucket expands to two // to the other new bucket (each oldbucket expands to two
// buckets during a grow). // buckets during a grow).
...@@ -879,12 +880,15 @@ next: ...@@ -879,12 +880,15 @@ next:
// repeatable and randomish choice of which direction // repeatable and randomish choice of which direction
// to send NaNs during evacuation. We'll use the low // to send NaNs during evacuation. We'll use the low
// bit of tophash to decide which way NaNs go. // bit of tophash to decide which way NaNs go.
// NOTE: this case is why we need two evacuate tophash
// values, evacuatedX and evacuatedY, that differ in
// their low bit.
if(check_bucket >> (it->B - 1) != (b->tophash[i] & 1)) { if(check_bucket >> (it->B - 1) != (b->tophash[i] & 1)) {
continue; continue;
} }
} }
} }
if(!evacuated(b)) { if(b->tophash[i] != EvacuatedX && b->tophash[i] != 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);
...@@ -920,7 +924,7 @@ next: ...@@ -920,7 +924,7 @@ next:
return; return;
} }
} }
b = overflowptr(b); b = b->overflow;
i = 0; i = 0;
goto next; goto next;
} }
......
...@@ -40,7 +40,7 @@ HASH_LOOKUP1(MapType *t, Hmap *h, KEYTYPE key, byte *value) ...@@ -40,7 +40,7 @@ HASH_LOOKUP1(MapType *t, Hmap *h, KEYTYPE key, byte *value)
b = (Bucket*)h->buckets; b = (Bucket*)h->buckets;
if(FASTKEY(key)) { if(FASTKEY(key)) {
for(i = 0, k = (KEYTYPE*)b->data, v = (byte*)(k + BUCKETSIZE); i < BUCKETSIZE; i++, k++, v += h->valuesize) { for(i = 0, k = (KEYTYPE*)b->data, v = (byte*)(k + BUCKETSIZE); i < BUCKETSIZE; i++, k++, v += h->valuesize) {
if(b->tophash[i] == 0) if(b->tophash[i] == Empty)
continue; continue;
if(QUICK_NE(key, *k)) if(QUICK_NE(key, *k))
continue; continue;
...@@ -53,7 +53,7 @@ HASH_LOOKUP1(MapType *t, Hmap *h, KEYTYPE key, byte *value) ...@@ -53,7 +53,7 @@ HASH_LOOKUP1(MapType *t, Hmap *h, KEYTYPE key, byte *value)
} else { } else {
keymaybe = -1; keymaybe = -1;
for(i = 0, k = (KEYTYPE*)b->data, v = (byte*)(k + BUCKETSIZE); i < BUCKETSIZE; i++, k++, v += h->valuesize) { for(i = 0, k = (KEYTYPE*)b->data, v = (byte*)(k + BUCKETSIZE); i < BUCKETSIZE; i++, k++, v += h->valuesize) {
if(b->tophash[i] == 0) if(b->tophash[i] == Empty)
continue; continue;
if(QUICK_NE(key, *k)) if(QUICK_NE(key, *k))
continue; continue;
...@@ -88,8 +88,8 @@ dohash: ...@@ -88,8 +88,8 @@ dohash:
bucket = h->hash0; bucket = h->hash0;
HASHFUNC(&bucket, sizeof(KEYTYPE), &key); HASHFUNC(&bucket, sizeof(KEYTYPE), &key);
top = bucket >> (sizeof(uintptr)*8 - 8); top = bucket >> (sizeof(uintptr)*8 - 8);
if(top == 0) if(top < MinTopHash)
top = 1; top += MinTopHash;
bucket &= (((uintptr)1 << h->B) - 1); bucket &= (((uintptr)1 << h->B) - 1);
if(h->oldbuckets != nil) { if(h->oldbuckets != nil) {
i = bucket & (((uintptr)1 << (h->B - 1)) - 1); i = bucket & (((uintptr)1 << (h->B - 1)) - 1);
...@@ -154,7 +154,7 @@ HASH_LOOKUP2(MapType *t, Hmap *h, KEYTYPE key, byte *value, bool res) ...@@ -154,7 +154,7 @@ HASH_LOOKUP2(MapType *t, Hmap *h, KEYTYPE key, byte *value, bool res)
b = (Bucket*)h->buckets; b = (Bucket*)h->buckets;
if(FASTKEY(key)) { if(FASTKEY(key)) {
for(i = 0, k = (KEYTYPE*)b->data, v = (byte*)(k + BUCKETSIZE); i < BUCKETSIZE; i++, k++, v += h->valuesize) { for(i = 0, k = (KEYTYPE*)b->data, v = (byte*)(k + BUCKETSIZE); i < BUCKETSIZE; i++, k++, v += h->valuesize) {
if(b->tophash[i] == 0) if(b->tophash[i] == Empty)
continue; continue;
if(QUICK_NE(key, *k)) if(QUICK_NE(key, *k))
continue; continue;
...@@ -169,7 +169,7 @@ HASH_LOOKUP2(MapType *t, Hmap *h, KEYTYPE key, byte *value, bool res) ...@@ -169,7 +169,7 @@ HASH_LOOKUP2(MapType *t, Hmap *h, KEYTYPE key, byte *value, bool res)
} else { } else {
keymaybe = -1; keymaybe = -1;
for(i = 0, k = (KEYTYPE*)b->data, v = (byte*)(k + BUCKETSIZE); i < BUCKETSIZE; i++, k++, v += h->valuesize) { for(i = 0, k = (KEYTYPE*)b->data, v = (byte*)(k + BUCKETSIZE); i < BUCKETSIZE; i++, k++, v += h->valuesize) {
if(b->tophash[i] == 0) if(b->tophash[i] == Empty)
continue; continue;
if(QUICK_NE(key, *k)) if(QUICK_NE(key, *k))
continue; continue;
...@@ -208,8 +208,8 @@ dohash: ...@@ -208,8 +208,8 @@ dohash:
bucket = h->hash0; bucket = h->hash0;
HASHFUNC(&bucket, sizeof(KEYTYPE), &key); HASHFUNC(&bucket, sizeof(KEYTYPE), &key);
top = bucket >> (sizeof(uintptr)*8 - 8); top = bucket >> (sizeof(uintptr)*8 - 8);
if(top == 0) if(top < MinTopHash)
top = 1; top += MinTopHash;
bucket &= (((uintptr)1 << h->B) - 1); bucket &= (((uintptr)1 << h->B) - 1);
if(h->oldbuckets != nil) { if(h->oldbuckets != nil) {
i = bucket & (((uintptr)1 << (h->B - 1)) - 1); i = bucket & (((uintptr)1 << (h->B - 1)) - 1);
......
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