Commit 00224a35 authored by Keith Randall's avatar Keith Randall

runtime: faster hashmap implementation.

Hashtable is arranged as an array of
8-entry buckets with chained overflow.
Each bucket has 8 extra hash bits
per key to provide quick lookup within
a bucket.  Table is grown incrementally.

Update #3885
Go time drops from 0.51s to 0.34s.

R=r, rsc, m3b, dave, bradfitz, khr, ugorji, remyoudompheng
CC=golang-dev
https://golang.org/cl/7504044
parent 2001f0c2
......@@ -63,7 +63,13 @@ char *runtimeimport =
"func @\"\".equal (@\"\".typ·2 *byte, @\"\".x1·3 any, @\"\".x2·4 any) (@\"\".ret·1 bool)\n"
"func @\"\".makemap (@\"\".mapType·2 *byte, @\"\".hint·3 int64) (@\"\".hmap·1 map[any]any)\n"
"func @\"\".mapaccess1 (@\"\".mapType·2 *byte, @\"\".hmap·3 map[any]any, @\"\".key·4 any) (@\"\".val·1 any)\n"
"func @\"\".mapaccess1_fast32 (@\"\".mapType·2 *byte, @\"\".hmap·3 map[any]any, @\"\".key·4 any) (@\"\".val·1 *any)\n"
"func @\"\".mapaccess1_fast64 (@\"\".mapType·2 *byte, @\"\".hmap·3 map[any]any, @\"\".key·4 any) (@\"\".val·1 *any)\n"
"func @\"\".mapaccess1_faststr (@\"\".mapType·2 *byte, @\"\".hmap·3 map[any]any, @\"\".key·4 any) (@\"\".val·1 *any)\n"
"func @\"\".mapaccess2 (@\"\".mapType·3 *byte, @\"\".hmap·4 map[any]any, @\"\".key·5 any) (@\"\".val·1 any, @\"\".pres·2 bool)\n"
"func @\"\".mapaccess2_fast32 (@\"\".mapType·3 *byte, @\"\".hmap·4 map[any]any, @\"\".key·5 any) (@\"\".val·1 *any, @\"\".pres·2 bool)\n"
"func @\"\".mapaccess2_fast64 (@\"\".mapType·3 *byte, @\"\".hmap·4 map[any]any, @\"\".key·5 any) (@\"\".val·1 *any, @\"\".pres·2 bool)\n"
"func @\"\".mapaccess2_faststr (@\"\".mapType·3 *byte, @\"\".hmap·4 map[any]any, @\"\".key·5 any) (@\"\".val·1 *any, @\"\".pres·2 bool)\n"
"func @\"\".mapassign1 (@\"\".mapType·1 *byte, @\"\".hmap·2 map[any]any, @\"\".key·3 any, @\"\".val·4 any)\n"
"func @\"\".mapiterinit (@\"\".mapType·1 *byte, @\"\".hmap·2 map[any]any, @\"\".hiter·3 *any)\n"
"func @\"\".mapdelete (@\"\".mapType·1 *byte, @\"\".hmap·2 map[any]any, @\"\".key·3 any)\n"
......
......@@ -182,8 +182,8 @@ walkrange(Node *n)
th = typ(TARRAY);
th->type = ptrto(types[TUINT8]);
// see ../../pkg/runtime/hashmap.h:/hash_iter
// Size in words.
th->bound = 5 + 4*3 + 4*4/widthptr;
// Size of hash_iter in # of pointers.
th->bound = 10;
hit = temp(th);
fn = syslook("mapiterinit", 1);
......
......@@ -636,8 +636,48 @@ walkexpr(Node **np, NodeList **init)
r = n->rlist->n;
walkexprlistsafe(n->list, init);
walkexpr(&r->left, init);
fn = mapfn("mapaccess2", r->left->type);
r = mkcall1(fn, getoutargx(fn->type), init, typename(r->left->type), r->left, r->right);
t = r->left->type;
p = nil;
if(t->type->width <= 128) { // Check ../../pkg/runtime/hashmap.c:MAXVALUESIZE before changing.
switch(simsimtype(t->down)) {
case TINT32:
case TUINT32:
p = "mapaccess2_fast32";
break;
case TINT64:
case TUINT64:
p = "mapaccess2_fast64";
break;
case TSTRING:
p = "mapaccess2_faststr";
break;
}
}
if(p != nil) {
// from:
// a,b = m[i]
// to:
// var,b = mapaccess2_fast*(t, m, i)
// a = *var
a = n->list->n;
var = temp(ptrto(t->type));
var->typecheck = 1;
fn = mapfn(p, t);
r = mkcall1(fn, getoutargx(fn->type), init, typename(t), r->left, r->right);
n->rlist = list1(r);
n->op = OAS2FUNC;
n->list->n = var;
walkexpr(&n, init);
*init = list(*init, n);
n = nod(OAS, a, nod(OIND, var, N));
typecheck(&n, Etop);
walkexpr(&n, init);
goto ret;
}
fn = mapfn("mapaccess2", t);
r = mkcall1(fn, getoutargx(fn->type), init, typename(t), r->left, r->right);
n->rlist = list1(r);
n->op = OAS2FUNC;
goto as2func;
......@@ -1004,7 +1044,33 @@ walkexpr(Node **np, NodeList **init)
goto ret;
t = n->left->type;
p = nil;
if(t->type->width <= 128) { // Check ../../pkg/runtime/hashmap.c:MAXVALUESIZE before changing.
switch(simsimtype(t->down)) {
case TINT32:
case TUINT32:
p = "mapaccess1_fast32";
break;
case TINT64:
case TUINT64:
p = "mapaccess1_fast64";
break;
case TSTRING:
p = "mapaccess1_faststr";
break;
}
}
if(p != nil) {
// use fast version. The fast versions return a pointer to the value - we need
// to dereference it to get the result.
n = mkcall1(mapfn(p, t), ptrto(t->type), init, typename(t), n->left, n->right);
n = nod(OIND, n, N);
n->type = t->type;
n->typecheck = 1;
} else {
// no fast version for this key
n = mkcall1(mapfn("mapaccess1", t), t->type, init, typename(t), n->left, n->right);
}
goto ret;
case ORECV:
......
This diff is collapsed.
This diff is collapsed.
......@@ -2,180 +2,28 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/* A hash table.
Example, hashing nul-terminated char*s:
hash_hash_t str_hash (void *v) {
char *s;
hash_hash_t hash = 0;
for (s = *(char **)v; *s != 0; s++) {
hash = (hash ^ *s) * 2654435769U;
}
return (hash);
}
int str_eq (void *a, void *b) {
return (strcmp (*(char **)a, *(char **)b) == 0);
}
void str_del (void *arg, void *data) {
*(char **)arg = *(char **)data;
}
struct hash *h = hash_new (sizeof (char *), &str_hash, &str_eq, &str_del, 3, 12, 15);
... 3=> 2**3 entries initial size
... 12=> 2**12 entries before sprouting sub-tables
... 15=> number of adjacent probes to attempt before growing
Example lookup:
char *key = "foobar";
char **result_ptr;
if (hash_lookup (h, &key, (void **) &result_ptr)) {
printf ("found in table: %s\n", *result_ptr);
} else {
printf ("not found in table\n");
}
Example insertion:
char *key = strdup ("foobar");
char **result_ptr;
if (hash_lookup (h, &key, (void **) &result_ptr)) {
printf ("found in table: %s\n", *result_ptr);
printf ("to overwrite, do *result_ptr = key\n");
} else {
printf ("not found in table; inserted as %s\n", *result_ptr);
assert (*result_ptr == key);
}
Example deletion:
char *key = "foobar";
char *result;
if (hash_remove (h, &key, &result)) {
printf ("key found and deleted from table\n");
printf ("called str_del (&result, data) to copy data to result: %s\n", result);
} else {
printf ("not found in table\n");
}
Example iteration over the elements of *h:
char **data;
struct hash_iter it;
hash_iter_init (h, &it);
for (data = hash_next (&it); data != 0; data = hash_next (&it)) {
printf ("%s\n", *data);
}
*/
#define memset(a,b,c) runtime·memclr((byte*)(a), (uint32)(c))
#define memcpy(a,b,c) runtime·memmove((byte*)(a),(byte*)(b),(uint32)(c))
#define assert(a) if(!(a)) runtime·throw("hashmap assert")
#define free(x) runtime·free(x)
#define memmove(a,b,c) runtime·memmove(a, b, c)
struct Hmap; /* opaque */
struct hash_subtable; /* opaque */
struct hash_entry; /* opaque */
typedef uintptr uintptr_t;
typedef uintptr_t hash_hash_t;
struct hash_iter {
uint8* data; /* returned from next */
int32 elemsize; /* size of elements in table */
int32 changes; /* number of changes observed last time */
int32 i; /* stack pointer in subtable_state */
bool cycled; /* have reached the end and wrapped to 0 */
hash_hash_t last_hash; /* last hash value returned */
hash_hash_t cycle; /* hash value where we started */
struct Hmap *h; /* the hash table */
MapType *t; /* the map type */
struct hash_iter_sub {
struct hash_entry *e; /* pointer into subtable */
struct hash_entry *start; /* start of subtable */
struct hash_entry *last; /* last entry in subtable */
} subtable_state[4]; /* Should be large enough unless the hashing is
so bad that many distinct data values hash
to the same hash value. */
};
/* Return a hashtable h 2**init_power empty entries, each with
"datasize" data bytes.
(*data_hash)(a) should return the hash value of data element *a.
(*data_eq)(a,b) should return whether the data at "a" and the data at "b"
are equal.
(*data_del)(arg, a) will be invoked when data element *a is about to be removed
from the table. "arg" is the argument passed to "hash_remove()".
Growing is accomplished by resizing if the current tables size is less than
a threshold, and by adding subtables otherwise. hint should be set
the expected maximum size of the table.
"datasize" should be in [sizeof (void*), ..., 255]. If you need a
bigger "datasize", store a pointer to another piece of memory. */
//struct hash *hash_new (int32 datasize,
// hash_hash_t (*data_hash) (void *),
// int32 (*data_eq) (void *, void *),
// void (*data_del) (void *, void *),
// int64 hint);
/* Lookup *data in *h. If the data is found, return 1 and place a pointer to
the found element in *pres. Otherwise return 0 and place 0 in *pres. */
// int32 hash_lookup (struct hash *h, void *data, void **pres);
/* Lookup *data in *h. If the data is found, execute (*data_del) (arg, p)
where p points to the data in the table, then remove it from *h and return
1. Otherwise return 0. */
// int32 hash_remove (struct hash *h, void *data, void *arg);
/* Lookup *data in *h. If the data is found, return 1, and place a pointer
to the found element in *pres. Otherwise, return 0, allocate a region
for the data to be inserted, and place a pointer to the inserted element
in *pres; it is the caller's responsibility to copy the data to be
inserted to the pointer returned in *pres in this case.
If using garbage collection, it is the caller's responsibility to
add references for **pres if HASH_ADDED is returned. */
// int32 hash_insert (struct hash *h, void *data, void **pres);
/* Return the number of elements in the table. */
// uint32 hash_count (struct hash *h);
/* The following call is useful only if not using garbage collection on the
table.
Remove all sub-tables associated with *h.
This undoes the effects of hash_init().
If other memory pointed to by user data must be freed, the caller is
responsible for doing so by iterating over *h first; see
hash_iter_init()/hash_next(). */
// void hash_destroy (struct hash *h);
/*----- iteration -----*/
/* Initialize *it from *h. */
// void hash_iter_init (struct hash *h, struct hash_iter *it);
/* Return the next used entry in the table with which *it was initialized. */
// void *hash_next (struct hash_iter *it);
/*---- test interface ----*/
/* Call (*data_visit) (arg, level, data) for every data entry in the table,
whether used or not. "level" is the subtable level, 0 means first level. */
/* TESTING ONLY: DO NOT USE THIS ROUTINE IN NORMAL CODE */
// void hash_visit (struct hash *h, void (*data_visit) (void *arg, int32 level, void *data), void *arg);
/* Used by the garbage collector */
struct hash_gciter
{
int32 elemsize;
uint8 flag;
uint8 valoff;
uint32 i; /* stack pointer in subtable_state */
struct hash_subtable *st;
struct hash_gciter_sub {
struct hash_entry *e; /* pointer into subtable */
struct hash_entry *last; /* last entry in subtable */
} subtable_state[4];
Hmap *h;
int32 phase;
uintptr bucket;
struct Bucket *b;
uintptr i;
};
// this data is used by the garbage collector to keep the map's
// internal structures from being reclaimed. The iterator must
// return in st every live object (ones returned by mallocgc) so
// that those objects won't be collected, and it must return
// every key & value in key_data/val_data so they can get scanned
// for pointers they point to. Note that if you malloc storage
// for keys and values, you need to do both.
struct hash_gciter_data
{
struct hash_subtable *st; /* subtable pointer, or nil */
uint8 *st; /* internal structure, or nil */
uint8 *key_data; /* key data, or nil */
uint8 *val_data; /* value data, or nil */
bool indirectkey; /* storing pointers to keys */
......
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Fast hashmap lookup specialized to a specific key type.
// Included by hashmap.c once for each specialized type.
// Note that this code differs from hash_lookup in that
// it returns a pointer to the result, not the result itself.
// The returned pointer is only valid until the next GC
// point, so the caller must dereference it before then.
// +build ignore
#pragma textflag 7
void
HASH_LOOKUP1(MapType *t, Hmap *h, KEYTYPE key, byte *value)
{
uintptr hash;
uintptr bucket;
Bucket *b;
uint8 top;
uintptr i;
KEYTYPE *k;
byte *v;
if(debug) {
runtime·prints("runtime.mapaccess1_fastXXX: map=");
runtime·printpointer(h);
runtime·prints("; key=");
t->key->alg->print(t->key->size, &key);
runtime·prints("\n");
}
if(h == nil || h->count == 0) {
value = empty_value;
FLUSH(&value);
return;
}
if(raceenabled)
runtime·racereadpc(h, runtime·getcallerpc(&t), HASH_LOOKUP1);
if(docheck)
check(t, h);
if(h->B == 0 && (h->count == 1 || QUICKEQ(key))) {
// One-bucket table. Don't hash, just check each bucket entry.
b = (Bucket*)h->buckets;
for(i = 0, k = (KEYTYPE*)b->data, v = (byte*)(k + BUCKETSIZE); i < BUCKETSIZE; i++, k++, v += h->valuesize) {
if(b->tophash[i] != 0 && EQFUNC(key, *k)) {
value = v;
FLUSH(&value);
return;
}
}
} else {
hash = h->hash0;
HASHFUNC(&hash, sizeof(KEYTYPE), &key);
bucket = hash & (((uintptr)1 << h->B) - 1);
if(h->oldbuckets != nil)
grow_work(t, h, bucket);
b = (Bucket*)(h->buckets + bucket * (offsetof(Bucket, data[0]) + BUCKETSIZE * sizeof(KEYTYPE) + BUCKETSIZE * h->valuesize));
top = hash >> (sizeof(uintptr)*8 - 8);
if(top == 0)
top = 1;
do {
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)) {
value = v;
FLUSH(&value);
return;
}
}
b = b->overflow;
} while(b != nil);
}
value = empty_value;
FLUSH(&value);
}
#pragma textflag 7
void
HASH_LOOKUP2(MapType *t, Hmap *h, KEYTYPE key, byte *value, bool res)
{
uintptr hash;
uintptr bucket;
Bucket *b;
uint8 top;
uintptr i;
KEYTYPE *k;
byte *v;
if(debug) {
runtime·prints("runtime.mapaccess2_fastXXX: map=");
runtime·printpointer(h);
runtime·prints("; key=");
t->key->alg->print(t->key->size, &key);
runtime·prints("\n");
}
if(h == nil || h->count == 0) {
value = empty_value;
res = false;
FLUSH(&value);
FLUSH(&res);
return;
}
if(raceenabled)
runtime·racereadpc(h, runtime·getcallerpc(&t), HASH_LOOKUP2);
if(docheck)
check(t, h);
if(h->B == 0 && (h->count == 1 || QUICKEQ(key))) {
// One-bucket table. Don't hash, just check each bucket entry.
b = (Bucket*)h->buckets;
for(i = 0, k = (KEYTYPE*)b->data, v = (byte*)(k + BUCKETSIZE); i < BUCKETSIZE; i++, k++, v += h->valuesize) {
if(b->tophash[i] != 0 && EQFUNC(key, *k)) {
value = v;
res = true;
FLUSH(&value);
FLUSH(&res);
return;
}
}
} else {
hash = h->hash0;
HASHFUNC(&hash, sizeof(KEYTYPE), &key);
bucket = hash & (((uintptr)1 << h->B) - 1);
if(h->oldbuckets != nil)
grow_work(t, h, bucket);
b = (Bucket*)(h->buckets + bucket * (offsetof(Bucket, data[0]) + BUCKETSIZE * sizeof(KEYTYPE) + BUCKETSIZE * h->valuesize));
top = hash >> (sizeof(uintptr)*8 - 8);
if(top == 0)
top = 1;
do {
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)) {
value = v;
res = true;
FLUSH(&value);
FLUSH(&res);
return;
}
}
b = b->overflow;
} while(b != nil);
}
value = empty_value;
res = false;
FLUSH(&value);
FLUSH(&res);
}
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime_test
import (
"fmt"
"math"
"runtime"
"sort"
"testing"
)
// negative zero is a good test because:
// 1) 0 and -0 are equal, yet have distinct representations.
// 2) 0 is represented as all zeros, -0 isn't.
// I'm not sure the language spec actually requires this behavior,
// but it's what the current map implementation does.
func TestNegativeZero(t *testing.T) {
m := make(map[float64]bool, 0)
m[+0.0] = true
m[math.Copysign(0.0, -1.0)] = true // should overwrite +0 entry
if len(m) != 1 {
t.Error("length wrong")
}
for k, _ := range m {
if math.Copysign(1.0, k) > 0 {
t.Error("wrong sign")
}
}
m = make(map[float64]bool, 0)
m[math.Copysign(0.0, -1.0)] = true
m[+0.0] = true // should overwrite -0.0 entry
if len(m) != 1 {
t.Error("length wrong")
}
for k, _ := range m {
if math.Copysign(1.0, k) < 0 {
t.Error("wrong sign")
}
}
}
// nan is a good test because nan != nan, and nan has
// a randomized hash value.
func TestNan(t *testing.T) {
m := make(map[float64]int, 0)
nan := math.NaN()
m[nan] = 1
m[nan] = 2
m[nan] = 4
if len(m) != 3 {
t.Error("length wrong")
}
s := 0
for k, v := range m {
if k == k {
t.Error("nan disappeared")
}
if (v & (v - 1)) != 0 {
t.Error("value wrong")
}
s |= v
}
if s != 7 {
t.Error("values wrong")
}
}
// Maps aren't actually copied on assignment.
func TestAlias(t *testing.T) {
m := make(map[int]int, 0)
m[0] = 5
n := m
n[0] = 6
if m[0] != 6 {
t.Error("alias didn't work")
}
}
func TestGrowWithNaN(t *testing.T) {
m := make(map[float64]int, 4)
nan := math.NaN()
m[nan] = 1
m[nan] = 2
m[nan] = 4
cnt := 0
s := 0
growflag := true
for k, v := range m {
if growflag {
// force a hashtable resize
for i := 0; i < 100; i++ {
m[float64(i)] = i
}
growflag = false
}
if k != k {
cnt++
s |= v
}
}
if cnt != 3 {
t.Error("NaN keys lost during grow")
}
if s != 7 {
t.Error("NaN values lost during grow")
}
}
type FloatInt struct {
x float64
y int
}
func TestGrowWithNegativeZero(t *testing.T) {
negzero := math.Copysign(0.0, -1.0)
m := make(map[FloatInt]int, 4)
m[FloatInt{0.0, 0}] = 1
m[FloatInt{0.0, 1}] = 2
m[FloatInt{0.0, 2}] = 4
m[FloatInt{0.0, 3}] = 8
growflag := true
s := 0
cnt := 0
negcnt := 0
// The first iteration should return the +0 key.
// The subsequent iterations should return the -0 key.
// I'm not really sure this is required by the spec,
// but it makes sense.
// TODO: are we allowed to get the first entry returned again???
for k, v := range m {
if v == 0 {
continue
} // ignore entries added to grow table
cnt++
if math.Copysign(1.0, k.x) < 0 {
if v&16 == 0 {
t.Error("key/value not updated together 1")
}
negcnt++
s |= v & 15
} else {
if v&16 == 16 {
t.Error("key/value not updated together 2", k, v)
}
s |= v
}
if growflag {
// force a hashtable resize
for i := 0; i < 100; i++ {
m[FloatInt{3.0, i}] = 0
}
// then change all the entries
// to negative zero
m[FloatInt{negzero, 0}] = 1 | 16
m[FloatInt{negzero, 1}] = 2 | 16
m[FloatInt{negzero, 2}] = 4 | 16
m[FloatInt{negzero, 3}] = 8 | 16
growflag = false
}
}
if s != 15 {
t.Error("entry missing", s)
}
if cnt != 4 {
t.Error("wrong number of entries returned by iterator", cnt)
}
if negcnt != 3 {
t.Error("update to negzero missed by iteration", negcnt)
}
}
func TestIterGrowAndDelete(t *testing.T) {
m := make(map[int]int, 4)
for i := 0; i < 100; i++ {
m[i] = i
}
growflag := true
for k := range m {
if growflag {
// grow the table
for i := 100; i < 1000; i++ {
m[i] = i
}
// delete all odd keys
for i := 1; i < 1000; i += 2 {
delete(m, i)
}
growflag = false
} else {
if k&1 == 1 {
t.Error("odd value returned")
}
}
}
}
// make sure old bucket arrays don't get GCd while
// an iterator is still using them.
func TestIterGrowWithGC(t *testing.T) {
m := make(map[int]int, 4)
for i := 0; i < 16; i++ {
m[i] = i
}
growflag := true
bitmask := 0
for k := range m {
if k < 16 {
bitmask |= 1 << uint(k)
}
if growflag {
// grow the table
for i := 100; i < 1000; i++ {
m[i] = i
}
// trigger a gc
runtime.GC()
growflag = false
}
}
if bitmask != 1<<16-1 {
t.Error("missing key", bitmask)
}
}
func TestBigItems(t *testing.T) {
var key [256]string
for i := 0; i < 256; i++ {
key[i] = "foo"
}
m := make(map[[256]string][256]string, 4)
for i := 0; i < 100; i++ {
key[37] = fmt.Sprintf("string%02d", i)
m[key] = key
}
var keys [100]string
var values [100]string
i := 0
for k, v := range m {
keys[i] = k[37]
values[i] = v[37]
i++
}
sort.Strings(keys[:])
sort.Strings(values[:])
for i := 0; i < 100; i++ {
if keys[i] != fmt.Sprintf("string%02d", i) {
t.Errorf("#%d: missing key: %v", i, keys[i])
}
if values[i] != fmt.Sprintf("string%02d", i) {
t.Errorf("#%d: missing value: %v", i, values[i])
}
}
}
type empty struct {
}
func TestEmptyKeyAndValue(t *testing.T) {
a := make(map[int]empty, 4)
b := make(map[empty]int, 4)
c := make(map[empty]empty, 4)
a[0] = empty{}
b[empty{}] = 0
b[empty{}] = 1
c[empty{}] = empty{}
if len(a) != 1 {
t.Errorf("empty value insert problem")
}
if b[empty{}] != 1 {
t.Errorf("empty key returned wrong value")
}
}
......@@ -5,6 +5,7 @@ package runtime_test
import (
"fmt"
"strings"
"testing"
)
......@@ -94,3 +95,56 @@ func BenchmarkHashStringArraySpeed(b *testing.B) {
}
}
}
func BenchmarkMegMap(b *testing.B) {
m := make(map[string]bool)
for suffix := 'A'; suffix <= 'G'; suffix++ {
m[strings.Repeat("X", 1<<20-1)+fmt.Sprint(suffix)] = true
}
key := strings.Repeat("X", 1<<20-1) + "k"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = m[key]
}
}
func BenchmarkMegOneMap(b *testing.B) {
m := make(map[string]bool)
m[strings.Repeat("X", 1<<20)] = true
key := strings.Repeat("Y", 1<<20)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = m[key]
}
}
func BenchmarkMegEmptyMap(b *testing.B) {
m := make(map[string]bool)
key := strings.Repeat("X", 1<<20)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = m[key]
}
}
func BenchmarkSmallStrMap(b *testing.B) {
m := make(map[string]bool)
for suffix := 'A'; suffix <= 'G'; suffix++ {
m[fmt.Sprint(suffix)] = true
}
key := "k"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = m[key]
}
}
func BenchmarkIntMap(b *testing.B) {
m := make(map[int]bool)
for i := 0; i < 8; i++ {
m[i] = true
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = m[7]
}
}
......@@ -42,9 +42,9 @@ runtime·gotraceback(bool *crash)
}
int32
runtime·mcmp(byte *s1, byte *s2, uint32 n)
runtime·mcmp(byte *s1, byte *s2, uintptr n)
{
uint32 i;
uintptr i;
byte c1, c2;
for(i=0; i<n; i++) {
......
......@@ -687,7 +687,7 @@ void runtime·panicstring(int8*);
void runtime·prints(int8*);
void runtime·printf(int8*, ...);
byte* runtime·mchr(byte*, byte, byte*);
int32 runtime·mcmp(byte*, byte*, uint32);
int32 runtime·mcmp(byte*, byte*, uintptr);
void runtime·memmove(void*, void*, uintptr);
void* runtime·mal(uintptr);
String runtime·catstring(String, String);
......@@ -962,7 +962,6 @@ void runtime·mapassign(MapType*, Hmap*, byte*, byte*);
void runtime·mapaccess(MapType*, Hmap*, byte*, byte*, bool*);
void runtime·mapiternext(struct hash_iter*);
bool runtime·mapiterkey(struct hash_iter*, void*);
void runtime·mapiterkeyvalue(struct hash_iter*, void*, void*);
Hmap* runtime·makemap_c(MapType*, int64);
Hchan* runtime·makechan_c(ChanType*, int64);
......
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