Skip to content

Commit

Permalink
feat: use hashstructure for hashmaps
Browse files Browse the repository at this point in the history
  • Loading branch information
joetifa2003 committed Apr 3, 2024
1 parent aa8319e commit 3c45a26
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 76 deletions.
88 changes: 49 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,55 @@
Golang manages memory via GC and it's good for almost every use case but sometimes it can be a bottleneck.
and this is where mm-go comes in to play.

- [mm-go Generic manual memory management for golang](#mm-go-generic-manual-memory-management-for-golang)
- [Before using mm-go](#before-using-mm-go)
- [Installing](#installing)
- [Packages](#packages)
- [typedarena](#typedarena)
- [Alloc/Free](#allocfree)
- [AllocMany/FreeMany](#allocmanyfreemany)
- [ReAlloc](#realloc)
- [vector](#vector)
- [Methods](#methods)
- [New](#new)
- [Init](#init)
- [Push](#push)
- [Pop](#pop)
- [Len](#len)
- [Cap](#cap)
- [Slice](#slice)
- [Last](#last)
- [At](#at)
- [AtPtr](#atptr)
- [Free](#free)
- [linkedlist](#linkedlist)
- [Methods](#methods-1)
- [New](#new-1)
- [PushBack](#pushback)
- [PushFront](#pushfront)
- [PopBack](#popback)
- [PopFront](#popfront)
- [ForEach](#foreach)
- [At](#at-1)
- [AtPtr](#atptr-1)
- [RemoveAt](#removeat)
- [Remove](#remove)
- [RemoveAll](#removeall)
- [FindIndex](#findindex)
- [FindIndexes](#findindexes)
- [Len](#len-1)
- [Free](#free-1)
- [Benchmarks](#benchmarks)
<!--toc:start-->
- [mm-go Generic manual memory management for golang](#mm-go-generic-manual-memory-management-for-golang)
- [Before using mm-go](#before-using-mm-go)
- [Installing](#installing)
- [Packages](#packages)
- [typedarena](#typedarena)
- [Alloc/Free](#allocfree)
- [AllocMany/FreeMany](#allocmanyfreemany)
- [ReAlloc](#realloc)
- [hashmap](#hashmap)
- [Methods](#methods)
- [New](#new)
- [Insert](#insert)
- [Delete](#delete)
- [Get](#get)
- [GetPtr](#getptr)
- [Free](#free)
- [vector](#vector)
- [Methods](#methods)
- [New](#new)
- [Init](#init)
- [Push](#push)
- [Pop](#pop)
- [Len](#len)
- [Cap](#cap)
- [Slice](#slice)
- [Last](#last)
- [At](#at)
- [AtPtr](#atptr)
- [Free](#free)
- [linkedlist](#linkedlist)
- [Methods](#methods)
- [New](#new)
- [PushBack](#pushback)
- [PushFront](#pushfront)
- [PopBack](#popback)
- [PopFront](#popfront)
- [ForEach](#foreach)
- [At](#at)
- [AtPtr](#atptr)
- [RemoveAt](#removeat)
- [Remove](#remove)
- [RemoveAll](#removeall)
- [FindIndex](#findindex)
- [FindIndexes](#findindexes)
- [Len](#len)
- [Free](#free)
- [Benchmarks](#benchmarks)
<!--toc:end-->

## Before using mm-go

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.7.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/ebitengine/purego v0.4.0-alpha.4 h1:Y7yIV06Yo5M2BAdD7EVPhfp6LZ0tEcQo5770OhYUVes=
github.com/ebitengine/purego v0.4.0-alpha.4/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
60 changes: 28 additions & 32 deletions hashmap/hashmap.go
Original file line number Diff line number Diff line change
@@ -1,52 +1,28 @@
package hashmap

import (
"hash/fnv"
"github.com/mitchellh/hashstructure/v2"

"github.com/joetifa2003/mm-go"
"github.com/joetifa2003/mm-go/linkedlist"
"github.com/joetifa2003/mm-go/vector"
)

// Hashable keys must implement this interface
// or use type hashmap.String and hashmap.Int
// which implements the interface
type Hashable interface {
comparable
Hash() uint32
}

// String a string type that implements Hashable Interface
type String string

func (s String) Hash() uint32 {
h := fnv.New32a()
h.Write([]byte(s))
return h.Sum32()
}

// Int an int type that implements Hashable Interface
type Int int

func (i Int) Hash() uint32 {
return uint32(i)
}

type pair[K Hashable, V any] struct {
type pair[K comparable, V any] struct {
key K
value V
}

// Hashmap Manually managed hashmap,
// keys can be hashmap.String, hashmap.Int or any type that
// implements the hashmap.Hashable interface
type Hashmap[K Hashable, V any] struct {
type Hashmap[K comparable, V any] struct {
pairs *vector.Vector[*linkedlist.LinkedList[pair[K, V]]]
totalTaken int
}

// New creates a new Hashmap with key of type K and value of type V
func New[K Hashable, V any]() *Hashmap[K, V] {
func New[K comparable, V any]() *Hashmap[K, V] {
hm := mm.Alloc[Hashmap[K, V]]()
hm.pairs = vector.New[*linkedlist.LinkedList[pair[K, V]]](8)
return hm
Expand Down Expand Up @@ -83,7 +59,12 @@ func (hm *Hashmap[K, V]) Insert(key K, value V) {
hm.extend()
}

idx := int(key.Hash() % uint32(hm.pairs.Len()))
hash, err := hashstructure.Hash(key, hashstructure.FormatV2, nil)
if err != nil {
panic(err)
}

idx := int(hash % uint64(hm.pairs.Len()))
pairs := hm.pairs.At(idx)
if pairs == nil {
newPairs := linkedlist.New[pair[K, V]]()
Expand All @@ -96,7 +77,12 @@ func (hm *Hashmap[K, V]) Insert(key K, value V) {

// Get takes key K and return value V
func (hm *Hashmap[K, V]) Get(key K) (value V, exists bool) {
idx := int(key.Hash() % uint32(hm.pairs.Len()))
hash, err := hashstructure.Hash(key, hashstructure.FormatV2, nil)
if err != nil {
panic(err)
}

idx := int(hash % uint64(hm.pairs.Len()))
pairs := hm.pairs.At(idx)
if pairs == nil {
return *new(V), false
Expand All @@ -114,7 +100,12 @@ func (hm *Hashmap[K, V]) Get(key K) (value V, exists bool) {

// GetPtr takes key K and return a pointer to value V
func (hm *Hashmap[K, V]) GetPtr(key K) (value *V, exists bool) {
idx := int(key.Hash() % uint32(hm.pairs.Len()))
hash, err := hashstructure.Hash(key, hashstructure.FormatV2, nil)
if err != nil {
panic(err)
}

idx := int(hash % uint64(hm.pairs.Len()))
pairs := hm.pairs.At(idx)
if pairs == nil {
return nil, false
Expand Down Expand Up @@ -179,7 +170,12 @@ func (hm *Hashmap[K, V]) Keys() []K {

// Delete delete value with key K
func (hm *Hashmap[K, V]) Delete(key K) {
idx := int(key.Hash() % uint32(hm.pairs.Len()))
hash, err := hashstructure.Hash(key, hashstructure.FormatV2, nil)
if err != nil {
panic(err)
}

idx := int(hash % uint64(hm.pairs.Len()))
pairs := hm.pairs.At(idx)
if pairs == nil {
return
Expand Down
13 changes: 8 additions & 5 deletions hashmap/hashmap_test.go
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
package hashmap_test

import (
"fmt"
"runtime"
"testing"

"github.com/joetifa2003/mm-go/hashmap"
"github.com/joetifa2003/mm-go/mmstring"
)

const TIMES = 5000

func BenchmarkHashmap(b *testing.B) {
for i := 0; i < b.N; i++ {
h := hashmap.New[hashmap.Int, int]()
h := hashmap.New[int, *mmstring.MMString]()

for i := 0; i < TIMES; i++ {
h.Insert(hashmap.Int(i), i)
h.Insert(i, mmstring.From("foo bar"))
}

h.Free()
runtime.GC()
}
}

func BenchmarkHashmapGo(b *testing.B) {
for i := 0; i < b.N; i++ {
h := map[string]int{}
h := map[string]string{}

for i := 0; i < TIMES; i++ {
h[fmt.Sprint(i)] = i
h["foo"] = "foo bar"
}

_ = h
runtime.GC()
}
Expand Down

0 comments on commit 3c45a26

Please sign in to comment.