diff --git a/README.md b/README.md index 416098f..442154b 100644 --- a/README.md +++ b/README.md @@ -6,60 +6,45 @@ 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) - - [hashmap](#hashmap) - - [Methods](#methods-2) - - [New](#new-2) - - [Insert](#insert) - - [Delete](#delete) - - [Get](#get) - - [GetPtr](#getptr) - - [Free](#free-2) - - [mmstring](#mmstring) - - [Methods](#methods-3) - - [New](#new-3) - - [From](#from) - - [GetGoString](#getgostring) - - [AppendGoString](#appendgostring) - - [Free](#free-3) - - [Benchmarks](#benchmarks) +- [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) ## Before using mm-go @@ -88,8 +73,6 @@ go get -u github.com/joetifa2003/mm-go `linkedlist` - contains a manually managed Linkedlist implementation. -`hashmap` - contains a manually managed Hashmap implementation. - `mmstring` - contains a manually managed string implementation. `malloc` - contains wrappers to raw C malloc and free. @@ -422,112 +405,6 @@ func (ll *LinkedList[T]) Len() int func (ll *LinkedList[T]) Free() ``` -## hashmap - -Manually managed hashmap, keys can be hashmap.String, hashmap.Int or any type that implements the hashmap.Hashable interface - -```go -type Hashable interface { - comparable - Hash() uint32 -} -``` - -### Methods - -#### New - -```go -// New creates a new Hashmap with key of type K and value of type V -func New[K Hashable, V any]() *Hashmap[K, V] -``` - -#### Insert - -```go -// Insert inserts a new value V if key K doesn't exist, -// Otherwise update the key K with value V -func (hm *Hashmap[K, V]) Insert(key K, value V) -``` - -#### Delete - -```go -// Delete delete value with key K -func (hm *Hashmap[K, V]) Delete(key K) -``` - -#### Get - -```go -// Get takes key K and return value V -func (hm *Hashmap[K, V]) Get(key K) (value V, exists bool) -``` - -#### GetPtr - -```go -// GetPtr takes key K and return a pointer to value V -func (hm *Hashmap[K, V]) GetPtr(key K) (value *V, exists bool) -``` - -#### Free - -```go -// Free frees the Hashmap -func (hm *Hashmap[K, V]) Free() -``` - -## mmstring - -MMString is a manually manged string that is basically a \*Vector[rune] -and contains all the methods of a vector plus additional helper functions - -```go -type MMString struct { - *vector.Vector[rune] -} -``` - -### Methods - -#### New - -```go -// New create a new manually managed string -func New() *MMString -``` - -#### From - -```go -// From creates a new manually managed string, -// And initialize it with a go string -func From(input string) *MMString -``` - -#### GetGoString - -```go -// GetGoString returns go string from manually managed string. -// CAUTION: You also have to free the MMString -func (s *MMString) GetGoString() string -``` - -#### AppendGoString - -```go -// AppendGoString appends go string to manually managed string -func (s *MMString) AppendGoString(input string) -``` - -#### Free - -```go -// Free frees MMString -func (s *MMString) Free() -``` - ## Benchmarks Check the test files and github actions for the benchmarks (linux, macos, windows). @@ -556,7 +433,4 @@ BinaryTreeArena/chunk_size_100-2 1.47s ± 5% BinaryTreeArena/chunk_size_150-2 1.42s ±36% BinaryTreeArena/chunk_size_250-2 1.11s ± 0% BinaryTreeArena/chunk_size_500-2 1.00s ± 0% -pkg:github.com/joetifa2003/mm-go/hashmap goos:linux goarch:amd64 -HashMap-2 14.8ms ± 0% -GoMap-2 6.39ms ± 1% ``` diff --git a/go.mod b/go.mod index 7d130ec..e80caec 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,15 @@ module github.com/joetifa2003/mm-go -go 1.19 +go 1.20 -require github.com/stretchr/testify v1.8.1 +require ( + github.com/ebitengine/purego v0.4.0-alpha.4 + github.com/stretchr/testify v1.8.1 +) require ( github.com/davecgh/go-spew v1.1.1 // 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 ) diff --git a/go.sum b/go.sum index 2ec90f7..a06a6aa 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/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= @@ -10,6 +12,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hashmap/hashmap.go b/hashmap/hashmap.go deleted file mode 100644 index 955627a..0000000 --- a/hashmap/hashmap.go +++ /dev/null @@ -1,202 +0,0 @@ -package hashmap - -import ( - "hash/fnv" - - "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 { - 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 { - 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] { - hm := mm.Alloc[Hashmap[K, V]]() - hm.pairs = vector.New[*linkedlist.LinkedList[pair[K, V]]](8) - return hm -} - -func (hm *Hashmap[K, V]) extend() { - newPairs := vector.New[*linkedlist.LinkedList[pair[K, V]]](hm.pairs.Len() * 2) - oldPairs := hm.pairs - defer oldPairs.Free() - - hm.totalTaken = 0 - hm.pairs = newPairs - - for _, pairs := range oldPairs.Slice() { - if pairs == nil { - continue - } - - pairs.ForEach(func(idx int, p pair[K, V]) { - hm.Insert(p.key, p.value) - }) - } -} - -// Insert inserts a new value V if key K doesn't exist, -// Otherwise update the key K with value V -func (hm *Hashmap[K, V]) Insert(key K, value V) { - if ptr, exists := hm.GetPtr(key); exists { - *ptr = value - return - } - - if hm.totalTaken == hm.pairs.Len() { - hm.extend() - } - - idx := int(key.Hash() % uint32(hm.pairs.Len())) - pairs := hm.pairs.At(idx) - if pairs == nil { - newPairs := linkedlist.New[pair[K, V]]() - hm.pairs.Set(idx, newPairs) - pairs = newPairs - } - pairs.PushBack(pair[K, V]{key: key, value: value}) - hm.totalTaken++ -} - -// 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())) - pairs := hm.pairs.At(idx) - if pairs == nil { - return *new(V), false - } - - pairIdx, ok := pairs.FindIndex(func(value pair[K, V]) bool { - return value.key == key - }) - if !ok { - return *new(V), false - } - - return pairs.At(pairIdx).value, ok -} - -// 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())) - pairs := hm.pairs.At(idx) - if pairs == nil { - return nil, false - } - - pairIdx, ok := pairs.FindIndex(func(value pair[K, V]) bool { - return value.key == key - }) - if !ok { - return nil, false - } - - return &pairs.AtPtr(pairIdx).value, ok -} - -// ForEach iterates through all key/value pairs -func (hm *Hashmap[K, V]) ForEach(f func(key K, value V)) { - for _, pairs := range hm.pairs.Slice() { - if pairs == nil { - continue - } - - pairs.ForEach(func(idx int, p pair[K, V]) { - f(p.key, p.value) - }) - } -} - -// Values returns all values as a slice -func (hm *Hashmap[K, V]) Values() []V { - res := make([]V, 0) - - for _, pairs := range hm.pairs.Slice() { - if pairs == nil { - continue - } - - pairs.ForEach(func(idx int, p pair[K, V]) { - res = append(res, p.value) - }) - } - - return res -} - -// Keys returns all keys as a slice -func (hm *Hashmap[K, V]) Keys() []K { - res := make([]K, 0) - - for _, pairs := range hm.pairs.Slice() { - if pairs == nil { - continue - } - - pairs.ForEach(func(idx int, p pair[K, V]) { - res = append(res, p.key) - }) - } - - return res -} - -// Delete delete value with key K -func (hm *Hashmap[K, V]) Delete(key K) { - idx := int(key.Hash() % uint32(hm.pairs.Len())) - pairs := hm.pairs.At(idx) - if pairs == nil { - return - } - - pairs.Remove(func(idx int, p pair[K, V]) bool { - return p.key == key - }) -} - -// Free frees the Hashmap -func (hm *Hashmap[K, V]) Free() { - for _, pairs := range hm.pairs.Slice() { - if pairs != nil { - pairs.Free() - } - } - hm.pairs.Free() - mm.Free(hm) -} diff --git a/hashmap/hashmap_test.go b/hashmap/hashmap_test.go deleted file mode 100644 index 25355e2..0000000 --- a/hashmap/hashmap_test.go +++ /dev/null @@ -1,141 +0,0 @@ -package hashmap_test - -import ( - "fmt" - "testing" - - "github.com/joetifa2003/mm-go/hashmap" - "github.com/stretchr/testify/assert" -) - -func TestHashmap(t *testing.T) { - t.Run("insert", testInsert) - t.Run("keys and values", testHashmapKeysValues) - - t.Run("delete", testDelete) -} - -func testInsert(t *testing.T) { - assert := assert.New(t) - - hm := hashmap.New[hashmap.String, string]() - defer hm.Free() - - const TIMES = 1000 - - for i := 0; i < TIMES; i++ { - hm.Insert(hashmap.String(fmt.Sprint(i)), fmt.Sprint(i)) - } - - for i := 0; i < TIMES; i++ { - if i%2 == 0 { - hm.Delete(hashmap.String(fmt.Sprint(i))) - } - } - - for i := 0; i < TIMES; i++ { - if i%2 == 0 { - continue - } - - value, exits := hm.Get(hashmap.String(fmt.Sprint(i))) - assert.Equal(fmt.Sprint(i), value) - assert.Equal(true, exits) - } - - hm.Insert("name", "Foo") - value, exists := hm.Get("name") - assert.Equal(true, exists) - assert.Equal("Foo", value) - - hm.Insert("name", "Bar") - value, exists = hm.Get("name") - assert.Equal(true, exists) - assert.Equal("Bar", value) -} - -func testHashmapKeysValues(t *testing.T) { - assert := assert.New(t) - - hm := hashmap.New[hashmap.String, int]() - defer hm.Free() - - hm.Insert("foo", 0) - hm.Insert("bar", 1) - - values := hm.Values() - assert.Equal(2, len(values)) - assert.Contains(values, 0) - assert.Contains(values, 1) - - keys := hm.Keys() - assert.Equal(2, len(keys)) - assert.Contains(keys, hashmap.String("foo")) - assert.Contains(keys, hashmap.String("bar")) - - type pair struct { - key hashmap.String - value int - } - - expectedPairs := []pair{ - {key: "foo", value: 0}, - {key: "bar", value: 1}, - } - - i := 0 - hm.ForEach(func(key hashmap.String, value int) { - assert.Contains(expectedPairs, pair{key: key, value: value}) - - i++ - }) - - assert.Equal(i, 2) -} - -func testDelete(t *testing.T) { - assert := assert.New(t) - - hm := hashmap.New[hashmap.Int, int]() - hm.Insert(1, 1) - assert.Equal([]int{1}, hm.Values()) - hm.Delete(1) - assert.Equal([]int{}, hm.Values()) -} - -const TIMES = 15000 - -func BenchmarkHashMap(b *testing.B) { - for n := 0; n < b.N; n++ { - hm := hashmap.New[hashmap.String, int]() - for i := 0; i < TIMES; i++ { - hm.Insert(hashmap.String(fmt.Sprint(i)), i) - } - - sum := 0 - for i := 0; i < TIMES; i++ { - v, _ := hm.Get(hashmap.String(fmt.Sprint(i))) - sum += v - } - - _ = sum - - hm.Free() - } -} - -func BenchmarkGoMap(b *testing.B) { - for n := 0; n < b.N; n++ { - hm := map[string]int{} - for i := 0; i < TIMES; i++ { - hm[fmt.Sprint(i)] = i - } - - sum := 0 - for i := 0; i < TIMES; i++ { - sum += hm[fmt.Sprint(i)] - } - - _ = sum - } -} diff --git a/malloc/malloc.go b/malloc/malloc.go index 3a351eb..1bef16b 100644 --- a/malloc/malloc.go +++ b/malloc/malloc.go @@ -1,20 +1,51 @@ package malloc -// #include -import "C" -import "unsafe" +import ( + "fmt" + "runtime" + "unsafe" + + "github.com/ebitengine/purego" +) + +var calloc func(n int, size int) unsafe.Pointer +var realloc func(ptr unsafe.Pointer, size int) unsafe.Pointer +var free func(ptr unsafe.Pointer) + +func getSystemLibrary() string { + switch runtime.GOOS { + case "darwin": + return "/usr/lib/libSystem.B.dylib" + case "linux": + return "libc.so.6" + case "windows": + return "ucrtbase.dll" + default: + panic(fmt.Errorf("GOOS=%s is not supported", runtime.GOOS)) + } +} + +func init() { + libc, err := openLibrary(getSystemLibrary()) + if err != nil { + panic(err) + } + purego.RegisterLibFunc(&calloc, libc, "calloc") + purego.RegisterLibFunc(&realloc, libc, "realloc") + purego.RegisterLibFunc(&free, libc, "free") +} // CMalloc raw binding to c calloc(1, size) func Malloc(size int) unsafe.Pointer { - return C.calloc(1, C.size_t(size)) + return calloc(1, size) } // CMalloc raw binding to c free func Free(ptr unsafe.Pointer) { - C.free(ptr) + free(ptr) } // CMalloc raw binding to c realloc func Realloc(ptr unsafe.Pointer, size int) unsafe.Pointer { - return C.realloc(ptr, C.size_t(size)) + return realloc(ptr, size) } diff --git a/malloc/malloc_unix.go b/malloc/malloc_unix.go new file mode 100644 index 0000000..b4e3b63 --- /dev/null +++ b/malloc/malloc_unix.go @@ -0,0 +1,9 @@ +//go:build darwin || linux + +package malloc + +import "github.com/ebitengine/purego" + +func openLibrary(name string) (uintptr, error) { + return purego.Dlopen(name, purego.RTLD_NOW|purego.RTLD_GLOBAL) +} diff --git a/malloc/malloc_windows.go b/malloc/malloc_windows.go new file mode 100644 index 0000000..62bfb23 --- /dev/null +++ b/malloc/malloc_windows.go @@ -0,0 +1,10 @@ +//go:build windows + +package malloc + +import "golang.org/x/sys/windows" + +func openLibrary(name string) (uintptr, error) { + handle, err := windows.LoadLibrary(name) + return uintptr(handle), err +}