mirror of
https://github.com/gogrlx/nats-server.git
synced 2026-04-02 03:38:42 -07:00
Removed old map
This commit is contained in:
193
map/map.go
193
map/map.go
@@ -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)}
|
||||
}
|
||||
236
map/map_test.go
236
map/map_test.go
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user