diff --git a/map/map.go b/map/map.go deleted file mode 100644 index 41a7c516..00000000 --- a/map/map.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2012 Apcera Inc. All rights reserved. - -// HashMap defines a high performance hashmap based on -// fast hashing and fast key comparison. Simple chaining -// is used, relying on the hashing algorithms for good -// distribution -package hashmap - -import ( - "bytes" - "errors" - "unsafe" - - "github.com/apcera/gnatsd/hash" -) - -// Entry represents what the map is actually storing. -// Uses simple linked list resolution for collisions. -type Entry struct { - hk uint32 - key []byte - data interface{} - next *Entry -} - -// BucketSize, must be power of 2 -const _BSZ = 8 - -// Constants for multiples of sizeof(WORD) -const ( - _WSZ = 4 // 4 - _DWSZ = _WSZ << 1 // 8 -) - -// DefaultHash to be used unless overridden. -var DefaultHash = hash.Jesteress - -// HashMap stores Entry items using a given Hash function. -// The Hash function can be overridden. -type HashMap struct { - Hash func([]byte) uint32 - bkts []*Entry - msk uint32 - used uint32 - rsz bool -} - -// Stats are reported on HashMaps -type Stats struct { - NumElements uint32 - NumSlots uint32 - NumBuckets uint32 - LongChain uint32 - AvgChain float32 -} - -// NewWithBkts creates a new HashMap using the bkts slice argument. -// len(bkts) must be a power of 2. -func NewWithBkts(bkts []*Entry) (*HashMap, error) { - l := len(bkts) - if l == 0 || (l&(l-1) != 0) { - return nil, errors.New("Size of buckets must be power of 2") - } - h := HashMap{} - h.msk = uint32(l - 1) - h.bkts = bkts - h.Hash = DefaultHash - h.rsz = true - return &h, nil -} - -// New creates a new HashMap of default size and using the default -// Hashing algorithm. -func New() *HashMap { - h, _ := NewWithBkts(make([]*Entry, _BSZ)) - return h -} - -func (h *HashMap) Set(key []byte, data interface{}) { - hk := h.Hash(key) - ne := &Entry{hk: hk, key: key, data: data} - ne.next = h.bkts[hk&h.msk] - h.bkts[hk&h.msk] = ne - h.used += 1 - // Check for resizing - if h.rsz && (h.used > uint32(len(h.bkts))) { - h.grow() - } -} - -func (h *HashMap) Get(key []byte) interface{} { - hk := h.Hash(key) - e := h.bkts[hk&h.msk] - - // FIXME: Reorder on GET if chained? - for e != nil && len(key) == len(e.key) { - // We unroll and optimize the key comparison here. - klen := len(key) - for i := 0; klen >= _DWSZ; klen -= _DWSZ { - k1 := *(*uint64)(unsafe.Pointer(&key[i])) - k2 := *(*uint64)(unsafe.Pointer(&e.key[i])) - if k1 != k2 { - goto next - } - i += _DWSZ - } - for i := 0; i < klen; i++ { - if key[i] != e.key[i] { - goto next - } - } - // Success - return e.data - next: - e = e.next - } - return nil -} - -func (h *HashMap) Remove(key []byte) { - hk := h.Hash(key) - e := &h.bkts[hk&h.msk] - for *e != nil { - if len(key) == len((*e).key) && bytes.Equal(key, (*e).key) { - // Success - *e = (*e).next - h.used -= 1 - // Check for resizing - lbkts := uint32(len(h.bkts)) - if h.rsz && lbkts > _BSZ && (h.used < lbkts/4) { - h.shrink() - } - return - } - e = &(*e).next - } -} - -// resize is responsible for reallocating the buckets and -// redistributing the hashmap entries. -func (h *HashMap) resize(nsz uint32) { - nmsk := nsz - 1 - bkts := make([]*Entry, nsz) - ents := make([]Entry, h.used) - var ne *Entry - var i int - for _, e := range h.bkts { - for ; e != nil; e = e.next { - ne, i = &ents[i], i+1 - *ne = *e - ne.next = bkts[e.hk&nmsk] - bkts[e.hk&nmsk] = ne - } - } - h.bkts = bkts - h.msk = nmsk -} - -// grow the HashMap's buckets by 2 -func (h *HashMap) grow() { - h.resize(uint32(2 * len(h.bkts))) -} - -// shrink the HashMap's buckets by 2 -func (h *HashMap) shrink() { - h.resize(uint32(len(h.bkts) / 2)) -} - -// Stats will collect general statistics about the HashMap -func (h *HashMap) Stats() *Stats { - lc, totalc, slots := 0, 0, 0 - for _, e := range h.bkts { - if e != nil { - slots += 1 - } - i := 0 - for ; e != nil; e = e.next { - i += 1 - if i > lc { - lc = i - } - } - totalc += i - } - l := uint32(len(h.bkts)) - avg := (float32(totalc) / float32(slots)) - return &Stats{ - NumElements: h.used, - NumBuckets: l, - LongChain: uint32(lc), - AvgChain: avg, - NumSlots: uint32(slots)} -} diff --git a/map/map_test.go b/map/map_test.go deleted file mode 100644 index 00a639eb..00000000 --- a/map/map_test.go +++ /dev/null @@ -1,236 +0,0 @@ -package hashmap - -import ( - "bytes" - "crypto/rand" - "encoding/hex" - "io" - "testing" -) - -func TestMapWithBkts(t *testing.T) { - bkts := make([]*Entry, 3, 3) - _, err := NewWithBkts(bkts) - if err == nil { - t.Fatalf("Buckets size of %d should have failed\n", len(bkts)) - } - bkts = make([]*Entry, 8, 8) - _, err = NewWithBkts(bkts) - if err != nil { - t.Fatalf("Buckets size of %d should have succeeded\n", len(bkts)) - } -} - -var foo = []byte("foo") -var bar = []byte("bar") -var baz = []byte("baz") -var sub = []byte("apcera.continuum.router.foo.bar.baz") - -func TestHashMapBasics(t *testing.T) { - h := New() - - if h.used != 0 { - t.Fatalf("Wrong number of entries: %d vs 0\n", h.used) - } - h.Set(foo, bar) - if h.used != 1 { - t.Fatalf("Wrong number of entries: %d vs 1\n", h.used) - } - if v := h.Get(foo).([]byte); !bytes.Equal(v, bar) { - t.Fatalf("Did not receive correct answer: '%s' vs '%s'\n", bar, v) - } - h.Remove(foo) - if h.used != 0 { - t.Fatalf("Wrong number of entries: %d vs 0\n", h.used) - } - if v := h.Get(foo); v != nil { - t.Fatal("Did not receive correct answer, should be nil") - } -} - -const ( - INS = 100 - EXP = 128 - REM = 75 - EXP2 = 64 -) - -func TestGrowing(t *testing.T) { - h := New() - - if len(h.bkts) != _BSZ { - t.Fatalf("Initial bucket size is wrong: %d vs %d\n", len(h.bkts), _BSZ) - } - // Create _INBOX style end tokens - var toks [INS][]byte - for i, _ := range toks { - u := make([]byte, 13) - io.ReadFull(rand.Reader, u) - toks[i] = []byte(hex.EncodeToString(u)) - h.Set(toks[i], toks[i]) - tg := h.Get(toks[i]).([]byte) - if !bytes.Equal(tg, toks[i]) { - t.Fatalf("Did not match properly, '%s' vs '%s'\n", tg, toks[i]) - } - } - if len(h.bkts) != EXP { - t.Fatalf("Expanded bucket size is wrong: %d vs %d\n", len(h.bkts), EXP) - } -} - -func TestHashMapCollisions(t *testing.T) { - h := New() - h.rsz = false - - // Create _INBOX style end tokens - var toks [INS][]byte - for i, _ := range toks { - u := make([]byte, 13) - io.ReadFull(rand.Reader, u) - toks[i] = []byte(hex.EncodeToString(u)) - h.Set(toks[i], toks[i]) - tg := h.Get(toks[i]).([]byte) - if !bytes.Equal(tg, toks[i]) { - t.Fatalf("Did not match properly, '%s' vs '%s'\n", tg, toks[i]) - } - } - if len(h.bkts) != _BSZ { - t.Fatalf("Bucket size is wrong: %d vs %d\n", len(h.bkts), _BSZ) - } - h.grow() - if len(h.bkts) != 2*_BSZ { - t.Fatalf("Bucket size is wrong: %d vs %d\n", len(h.bkts), 2*_BSZ) - } - ti := 32 - tg := h.Get(toks[ti]).([]byte) - if !bytes.Equal(tg, toks[ti]) { - t.Fatalf("Did not match properly, '%s' vs '%s'\n", tg, toks[ti]) - } - - h.Remove(toks[99]) - rg := h.Get(toks[99]) - if rg != nil { - t.Fatalf("After remove should have been nil! '%s'\n", rg.([]byte)) - } -} - -func TestHashMapStats(t *testing.T) { - h := New() - h.rsz = false - - // Create _INBOX style end tokens - var toks [INS][]byte - for i, _ := range toks { - u := make([]byte, 13) - io.ReadFull(rand.Reader, u) - toks[i] = []byte(hex.EncodeToString(u)) - h.Set(toks[i], toks[i]) - tg := h.Get(toks[i]).([]byte) - if !bytes.Equal(tg, toks[i]) { - t.Fatalf("Did not match properly, '%s' vs '%s'\n", tg, toks[i]) - } - } - - s := h.Stats() - if s.NumElements != INS { - t.Fatalf("NumElements incorrect: %d vs %d\n", s.NumElements, INS) - } - if s.NumBuckets != _BSZ { - t.Fatalf("NumBuckets incorrect: %d vs %d\n", s.NumBuckets, _BSZ) - } - if s.AvgChain > 13 || s.AvgChain < 12 { - t.Fatalf("AvgChain out of bounds: %f vs %f\n", s.AvgChain, 12.5) - } - if s.LongChain > 18 { - t.Fatalf("LongChain out of bounds: %d vs %f\n", s.LongChain, 18) - } -} - -func TestShrink(t *testing.T) { - h := New() - - if len(h.bkts) != _BSZ { - t.Fatalf("Initial bucket size is wrong: %d vs %d\n", len(h.bkts), _BSZ) - } - // Create _INBOX style end tokens - var toks [INS][]byte - for i, _ := range toks { - u := make([]byte, 13) - io.ReadFull(rand.Reader, u) - toks[i] = []byte(hex.EncodeToString(u)) - h.Set(toks[i], toks[i]) - tg := h.Get(toks[i]).([]byte) - if !bytes.Equal(tg, toks[i]) { - t.Fatalf("Did not match properly, '%s' vs '%s'\n", tg, toks[i]) - } - } - if len(h.bkts) != EXP { - t.Fatalf("Expanded bucket size is wrong: %d vs %d\n", len(h.bkts), EXP) - } - for i := 0; i < REM; i++ { - h.Remove(toks[i]) - } - if len(h.bkts) != EXP2 { - t.Fatalf("Shrunk bucket size is wrong: %d vs %d\n", len(h.bkts), EXP2) - } -} - -func Benchmark_GoMap___GetSmallKey(b *testing.B) { - b.StopTimer() - b.SetBytes(1) - m := make(map[string][]byte) - m["foo"] = bar - b.StartTimer() - - for i := 0; i < b.N; i++ { - _ = m["foo"] - } -} - -func Benchmark_HashMap_GetSmallKey(b *testing.B) { - b.StopTimer() - b.SetBytes(1) - m := New() - m.Set(foo, bar) - b.StartTimer() - - for i := 0; i < b.N; i++ { - _ = m.Get(foo) - } -} - -func Benchmark_GoMap____GetMedKey(b *testing.B) { - b.StopTimer() - b.SetBytes(1) - ts := string(sub) - m := make(map[string][]byte) - m[ts] = bar - b.StartTimer() - - for i := 0; i < b.N; i++ { - _ = m[ts] - } -} - -func Benchmark_HashMap__GetMedKey(b *testing.B) { - b.StopTimer() - b.SetBytes(1) - m := New() - m.Set(sub, bar) - b.StartTimer() - - for i := 0; i < b.N; i++ { - m.Get(sub) - } -} - -func Benchmark_HashMap_______Set(b *testing.B) { - b.StopTimer() - b.SetBytes(1) - m := New() - b.StartTimer() - - for i := 0; i < b.N; i++ { - m.Set(foo, bar) - } -}