Commit a696ae56 authored by Keith Randall's avatar Keith Randall

runtime: optimize some hash lookups.

When comparing strings, check these (in order):
- length mismatch => not equal
- string pointer equal => equal
- if length is short:
  - memeq on body
- if length is long:
  - compare first&last few bytes, if different => not equal
  - save entry as a possible match
  - after checking every entry, if there is only one possible
    match, use memeq on that entry.  Otherwise, fallback to hash.

benchmark                 old ns/op    new ns/op    delta
BenchmarkSameLengthMap           43            4  -89.77%

Fixes #5194.
Update #3885.

R=golang-dev, bradfitz, khr, rsc
CC=golang-dev
https://golang.org/cl/12128044
parent 609d742e
...@@ -533,48 +533,65 @@ static uint8 empty_value[MAXVALUESIZE]; ...@@ -533,48 +533,65 @@ static uint8 empty_value[MAXVALUESIZE];
#define HASH_LOOKUP2 runtime·mapaccess2_fast32 #define HASH_LOOKUP2 runtime·mapaccess2_fast32
#define KEYTYPE uint32 #define KEYTYPE uint32
#define HASHFUNC runtime·algarray[AMEM32].hash #define HASHFUNC runtime·algarray[AMEM32].hash
#define EQFUNC(x,y) ((x) == (y)) #define FASTKEY(x) true
#define EQMAYBE(x,y) ((x) == (y)) #define QUICK_NE(x,y) ((x) != (y))
#define HASMAYBE false #define QUICK_EQ(x,y) true
#define QUICKEQ(x) true #define SLOW_EQ(x,y) true
#define MAYBE_EQ(x,y) true
#include "hashmap_fast.c" #include "hashmap_fast.c"
#undef HASH_LOOKUP1 #undef HASH_LOOKUP1
#undef HASH_LOOKUP2 #undef HASH_LOOKUP2
#undef KEYTYPE #undef KEYTYPE
#undef HASHFUNC #undef HASHFUNC
#undef EQFUNC #undef FASTKEY
#undef EQMAYBE #undef QUICK_NE
#undef HASMAYBE #undef QUICK_EQ
#undef QUICKEQ #undef SLOW_EQ
#undef MAYBE_EQ
#define HASH_LOOKUP1 runtime·mapaccess1_fast64 #define HASH_LOOKUP1 runtime·mapaccess1_fast64
#define HASH_LOOKUP2 runtime·mapaccess2_fast64 #define HASH_LOOKUP2 runtime·mapaccess2_fast64
#define KEYTYPE uint64 #define KEYTYPE uint64
#define HASHFUNC runtime·algarray[AMEM64].hash #define HASHFUNC runtime·algarray[AMEM64].hash
#define EQFUNC(x,y) ((x) == (y)) #define FASTKEY(x) true
#define EQMAYBE(x,y) ((x) == (y)) #define QUICK_NE(x,y) ((x) != (y))
#define HASMAYBE false #define QUICK_EQ(x,y) true
#define QUICKEQ(x) true #define SLOW_EQ(x,y) true
#define MAYBE_EQ(x,y) true
#include "hashmap_fast.c" #include "hashmap_fast.c"
#undef HASH_LOOKUP1 #undef HASH_LOOKUP1
#undef HASH_LOOKUP2 #undef HASH_LOOKUP2
#undef KEYTYPE #undef KEYTYPE
#undef HASHFUNC #undef HASHFUNC
#undef EQFUNC #undef FASTKEY
#undef EQMAYBE #undef QUICK_NE
#undef HASMAYBE #undef QUICK_EQ
#undef QUICKEQ #undef SLOW_EQ
#undef MAYBE_EQ
#ifdef GOARCH_amd64
#define CHECKTYPE uint64
#endif
#ifdef GOARCH_386
#define CHECKTYPE uint32
#endif
#ifdef GOARCH_arm
// can't use uint32 on arm because our loads aren't aligned.
// TODO: use uint32 for arm v6+?
#define CHECKTYPE uint8
#endif
#define HASH_LOOKUP1 runtime·mapaccess1_faststr #define HASH_LOOKUP1 runtime·mapaccess1_faststr
#define HASH_LOOKUP2 runtime·mapaccess2_faststr #define HASH_LOOKUP2 runtime·mapaccess2_faststr
#define KEYTYPE String #define KEYTYPE String
#define HASHFUNC runtime·algarray[ASTRING].hash #define HASHFUNC runtime·algarray[ASTRING].hash
#define EQFUNC(x,y) ((x).len == (y).len && ((x).str == (y).str || runtime·memeq((x).str, (y).str, (x).len))) #define FASTKEY(x) ((x).len < 32)
#define EQMAYBE(x,y) ((x).len == (y).len) #define QUICK_NE(x,y) ((x).len != (y).len)
#define HASMAYBE true #define QUICK_EQ(x,y) ((x).str == (y).str)
#define QUICKEQ(x) ((x).len < 32) #define SLOW_EQ(x,y) runtime·memeq((x).str, (y).str, (x).len)
#define MAYBE_EQ(x,y) (*(CHECKTYPE*)(x).str == *(CHECKTYPE*)(y).str && *(CHECKTYPE*)((x).str + (x).len - sizeof(CHECKTYPE)) == *(CHECKTYPE*)((y).str + (x).len - sizeof(CHECKTYPE)))
#include "hashmap_fast.c" #include "hashmap_fast.c"
static void static void
......
...@@ -22,7 +22,6 @@ HASH_LOOKUP1(MapType *t, Hmap *h, KEYTYPE key, byte *value) ...@@ -22,7 +22,6 @@ HASH_LOOKUP1(MapType *t, Hmap *h, KEYTYPE key, byte *value)
byte *v; byte *v;
uint8 top; uint8 top;
int8 keymaybe; int8 keymaybe;
bool quickkey;
if(debug) { if(debug) {
runtime·prints("runtime.mapaccess1_fastXXX: map="); runtime·prints("runtime.mapaccess1_fastXXX: map=");
...@@ -43,37 +42,50 @@ HASH_LOOKUP1(MapType *t, Hmap *h, KEYTYPE key, byte *value) ...@@ -43,37 +42,50 @@ HASH_LOOKUP1(MapType *t, Hmap *h, KEYTYPE key, byte *value)
if(h->B == 0) { if(h->B == 0) {
// One-bucket table. Don't hash, just check each bucket entry. // One-bucket table. Don't hash, just check each bucket entry.
if(HASMAYBE) {
keymaybe = -1;
}
quickkey = QUICKEQ(key);
b = (Bucket*)h->buckets; b = (Bucket*)h->buckets;
for(i = 0, k = (KEYTYPE*)b->data, v = (byte*)(k + BUCKETSIZE); i < BUCKETSIZE; i++, k++, v += h->valuesize) { if(FASTKEY(key)) {
if(b->tophash[i] != 0) { for(i = 0, k = (KEYTYPE*)b->data, v = (byte*)(k + BUCKETSIZE); i < BUCKETSIZE; i++, k++, v += h->valuesize) {
if(quickkey && EQFUNC(key, *k)) { if(b->tophash[i] == 0)
continue;
if(QUICK_NE(key, *k))
continue;
if(QUICK_EQ(key, *k) || SLOW_EQ(key, *k)) {
value = v;
FLUSH(&value);
return;
}
}
} else {
keymaybe = -1;
for(i = 0, k = (KEYTYPE*)b->data, v = (byte*)(k + BUCKETSIZE); i < BUCKETSIZE; i++, k++, v += h->valuesize) {
if(b->tophash[i] == 0)
continue;
if(QUICK_NE(key, *k))
continue;
if(QUICK_EQ(key, *k)) {
value = v; value = v;
FLUSH(&value); FLUSH(&value);
return; return;
} }
if(HASMAYBE && EQMAYBE(key, *k)) { if(MAYBE_EQ(key, *k)) {
// TODO: check if key.str matches. Add EQFUNCFAST?
if(keymaybe >= 0) { if(keymaybe >= 0) {
// Two same-length strings in this bucket. // Two same-length strings in this bucket.
// use slow path. // use slow path.
// TODO: keep track of more than just 1. Especially // TODO: keep track of more than just 1. We could
// if doing the TODO above. // afford about 3 equals calls before it would be more
// expensive than 1 hash + 1 equals.
goto dohash; goto dohash;
} }
keymaybe = i; keymaybe = i;
} }
} }
} if(keymaybe >= 0) {
if(HASMAYBE && keymaybe >= 0) { k = (KEYTYPE*)b->data + keymaybe;
k = (KEYTYPE*)b->data + keymaybe; if(SLOW_EQ(key, *k)) {
if(EQFUNC(key, *k)) { value = (byte*)((KEYTYPE*)b->data + BUCKETSIZE) + keymaybe * h->valuesize;
value = (byte*)((KEYTYPE*)b->data + BUCKETSIZE) + keymaybe * h->valuesize; FLUSH(&value);
FLUSH(&value); return;
return; }
} }
} }
} else { } else {
...@@ -95,7 +107,11 @@ dohash: ...@@ -95,7 +107,11 @@ dohash:
} }
do { do {
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] == top && EQFUNC(key, *k)) { if(b->tophash[i] != top)
continue;
if(QUICK_NE(key, *k))
continue;
if(QUICK_EQ(key, *k) || SLOW_EQ(key, *k)) {
value = v; value = v;
FLUSH(&value); FLUSH(&value);
return; return;
...@@ -118,7 +134,6 @@ HASH_LOOKUP2(MapType *t, Hmap *h, KEYTYPE key, byte *value, bool res) ...@@ -118,7 +134,6 @@ HASH_LOOKUP2(MapType *t, Hmap *h, KEYTYPE key, byte *value, bool res)
byte *v; byte *v;
uint8 top; uint8 top;
int8 keymaybe; int8 keymaybe;
bool quickkey;
if(debug) { if(debug) {
runtime·prints("runtime.mapaccess2_fastXXX: map="); runtime·prints("runtime.mapaccess2_fastXXX: map=");
...@@ -140,42 +155,57 @@ HASH_LOOKUP2(MapType *t, Hmap *h, KEYTYPE key, byte *value, bool res) ...@@ -140,42 +155,57 @@ HASH_LOOKUP2(MapType *t, Hmap *h, KEYTYPE key, byte *value, bool res)
check(t, h); check(t, h);
if(h->B == 0) { if(h->B == 0) {
// One-bucket table. Don't hash, just check each bucket entry. // One-bucket table. Don't hash, just check each bucket entry.
if(HASMAYBE) {
keymaybe = -1;
}
quickkey = QUICKEQ(key);
b = (Bucket*)h->buckets; b = (Bucket*)h->buckets;
for(i = 0, k = (KEYTYPE*)b->data, v = (byte*)(k + BUCKETSIZE); i < BUCKETSIZE; i++, k++, v += h->valuesize) { if(FASTKEY(key)) {
if(b->tophash[i] != 0) { for(i = 0, k = (KEYTYPE*)b->data, v = (byte*)(k + BUCKETSIZE); i < BUCKETSIZE; i++, k++, v += h->valuesize) {
if(quickkey && EQFUNC(key, *k)) { if(b->tophash[i] == 0)
continue;
if(QUICK_NE(key, *k))
continue;
if(QUICK_EQ(key, *k) || SLOW_EQ(key, *k)) {
value = v; value = v;
res = true; res = true;
FLUSH(&value); FLUSH(&value);
FLUSH(&res); FLUSH(&res);
return; return;
} }
if(HASMAYBE && EQMAYBE(key, *k)) { }
// TODO: check if key.str matches. Add EQFUNCFAST? } else {
keymaybe = -1;
for(i = 0, k = (KEYTYPE*)b->data, v = (byte*)(k + BUCKETSIZE); i < BUCKETSIZE; i++, k++, v += h->valuesize) {
if(b->tophash[i] == 0)
continue;
if(QUICK_NE(key, *k))
continue;
if(QUICK_EQ(key, *k)) {
value = v;
res = true;
FLUSH(&value);
FLUSH(&res);
return;
}
if(MAYBE_EQ(key, *k)) {
if(keymaybe >= 0) { if(keymaybe >= 0) {
// Two same-length strings in this bucket. // Two same-length strings in this bucket.
// use slow path. // use slow path.
// TODO: keep track of more than just 1. Especially // TODO: keep track of more than just 1. We could
// if doing the TODO above. // afford about 3 equals calls before it would be more
// expensive than 1 hash + 1 equals.
goto dohash; goto dohash;
} }
keymaybe = i; keymaybe = i;
} }
} }
} if(keymaybe >= 0) {
if(HASMAYBE && keymaybe >= 0) { k = (KEYTYPE*)b->data + keymaybe;
k = (KEYTYPE*)b->data + keymaybe; if(SLOW_EQ(key, *k)) {
if(EQFUNC(key, *k)) { value = (byte*)((KEYTYPE*)b->data + BUCKETSIZE) + keymaybe * h->valuesize;
value = (byte*)((KEYTYPE*)b->data + BUCKETSIZE) + keymaybe * h->valuesize; res = true;
res = true; FLUSH(&value);
FLUSH(&value); FLUSH(&res);
FLUSH(&res); return;
return; }
} }
} }
} else { } else {
...@@ -197,7 +227,11 @@ dohash: ...@@ -197,7 +227,11 @@ dohash:
} }
do { do {
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] == top && EQFUNC(key, *k)) { if(b->tophash[i] != top)
continue;
if(QUICK_NE(key, *k))
continue;
if(QUICK_EQ(key, *k) || SLOW_EQ(key, *k)) {
value = v; value = v;
res = true; res = true;
FLUSH(&value); FLUSH(&value);
......
...@@ -254,3 +254,17 @@ func BenchmarkMapIterEmpty(b *testing.B) { ...@@ -254,3 +254,17 @@ func BenchmarkMapIterEmpty(b *testing.B) {
} }
} }
} }
func BenchmarkSameLengthMap(b *testing.B) {
// long strings, same length, differ in first few
// and last few bytes.
m := make(map[string]bool)
s1 := "foo" + strings.Repeat("-", 100) + "bar"
s2 := "goo" + strings.Repeat("-", 100) + "ber"
m[s1] = true
m[s2] = true
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = m[s1]
}
}
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