Files
nats-server/hashmap/hashmap.go
2015-06-19 12:08:11 -07:00

258 lines
5.3 KiB
Go

// Copyright 2012-2015 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/nats-io/gnatsd/hash"
)
// 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
}
// 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
// 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
}
// Set will set the key item to data. This will blindly replace any item
// that may have been at key previous.
func (h *HashMap) Set(key []byte, data interface{}) {
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, replace data field
e.data = data
return
}
e = e.next
}
// We have a new entry here
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()
}
}
// Get will return the item at key.
func (h *HashMap) Get(key []byte) interface{} {
hk := h.Hash(key)
e := h.bkts[hk&h.msk]
// FIXME: Reorder on GET if chained?
// We unroll and optimize the comparison of keys.
for e != nil {
i, klen := 0, len(key)
if klen != len(e.key) {
goto next
}
// We unroll and optimize the key comparison here.
// Compare _DWSZ at a time
for ; klen >= _DWSZ; klen -= _DWSZ {
k1 := *(*uint64)(unsafe.Pointer(&key[i]))
k2 := *(*uint64)(unsafe.Pointer(&e.key[i]))
if k1 != k2 {
goto next
}
i += _DWSZ
}
// Check by _WSZ if applicable
if (klen & _WSZ) > 0 {
k1 := *(*uint32)(unsafe.Pointer(&key[i]))
k2 := *(*uint32)(unsafe.Pointer(&e.key[i]))
if k1 != k2 {
goto next
}
i += _WSZ
}
// Compare what is left over, byte by byte
for ; i < len(key); i++ {
if key[i] != e.key[i] {
goto next
}
}
// Success
return e.data
next:
e = e.next
}
return nil
}
// Remove will remove what is associated with key.
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
}
const maxBktSize = (1 << 31) - 1
// grow the HashMap's buckets by 2
func (h *HashMap) grow() {
// Can't grow beyond maxint for now
if len(h.bkts) >= maxBktSize {
return
}
h.resize(uint32(2 * len(h.bkts)))
}
// shrink the HashMap's buckets by 2
func (h *HashMap) shrink() {
if len(h.bkts) <= _BSZ {
return
}
h.resize(uint32(len(h.bkts) / 2))
}
// Count returns number of elements in the HashMap
func (h *HashMap) Count() uint32 {
return h.used
}
// AllKeys will return all the keys stored in the HashMap
func (h *HashMap) AllKeys() [][]byte {
all := make([][]byte, 0, h.used)
for _, e := range h.bkts {
for ; e != nil; e = e.next {
all = append(all, e.key)
}
}
return all
}
// All returns all the Entries in the map
func (h *HashMap) All() []interface{} {
all := make([]interface{}, 0, h.used)
for _, e := range h.bkts {
for ; e != nil; e = e.next {
all = append(all, e.data)
}
}
return all
}
// 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)}
}