diff --git a/README.md b/README.md index 91e7cfa..a675632 100644 --- a/README.md +++ b/README.md @@ -3,24 +3,6 @@ # mm-go Generic manual memory management for golang - -- [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) - - [Allocators](#allocators) - - [C allocator](#c-allocator) - - [BatchAllocator](#batchallocator) - - [Generic Helpers](#generic-helpers) - - [Alloc/Free](#allocfree) - - [AllocMany/FreeMany](#allocmanyfreemany) - - [ReAlloc](#realloc) - - [typedarena](#typedarena) - - [Why does this exists while there is BatchAllocator?](#why-does-this-exists-while-there-is-batchallocator) - - [vector](#vector) - - [Benchmarks](#benchmarks) - - 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. @@ -41,323 +23,1232 @@ and this is where mm-go comes in to play. go get -u github.com/joetifa2003/mm-go ``` -## Packages +## Benchmarks + +Check the test files and github actions for the benchmarks (linux, macos, windows). +mm-go can sometimes be 5-10 times faster. -`mm` - basic generic memory management functions. +``` +Run go test ./... -bench=. -count 5 > out.txt && benchstat out.txt -`typedarena` - contains TypedArena which allocates many objects and free them all at once. +name time/op +pkg:github.com/joetifa2003/mm-go goos:linux goarch:amd64 +HeapManaged/node_count_10000-2 504µs ± 1% +HeapManaged/node_count_100000-2 3.73ms ± 6% +HeapManaged/node_count_10000000-2 664ms ± 8% +HeapManaged/node_count_100000000-2 6.30s ± 4% +Manual/node_count_10000-2 226µs ± 1% +Manual/node_count_100000-2 576µs ± 1% +Manual/node_count_10000000-2 70.6ms ± 1% +Manual/node_count_100000000-2 702ms ± 1% +ArenaManual/node_count_10000-2 226µs ± 1% +ArenaManual/node_count_100000-2 553µs ± 0% +ArenaManual/node_count_10000000-2 69.1ms ± 0% +ArenaManual/node_count_100000000-2 681ms ± 1% +BinaryTreeManaged-2 6.07s ±10% +BinaryTreeArena/chunk_size_50-2 2.30s ±21% +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% +``` -`vector` - contains a manually managed Vector implementation. + -`linkedlist` - contains a manually managed Linkedlist implementation. + -`mmstring` - contains a manually managed string implementation. +# mm -`malloc` - contains wrappers to raw C malloc and free. +```go +import "github.com/joetifa2003/mm-go" +``` -`allocator` - contains the Allocator interface and the C allocator implementation. +## Index -`batchallocator` - contains implementation an allocator, this can be used as an arena, and a way to reduce CGO overhead. +- [func SizeOf\[T any\]\(\) int](<#SizeOf>) -## Allocators -Allocator is an interface that defines some methods needed for most allocators. + +## func [SizeOf]() ```go -Alloc(size int) unsafe.Pointer // returns a pointer to the allocated memory -Free(ptr unsafe.Pointer) // frees the memory pointed by ptr -Realloc(ptr unsafe.Pointer, size int) unsafe.Pointer // reallocates the memory pointed by ptr -Destroy() // any cleanup that the allocator needs to do +func SizeOf[T any]() int ``` -Currently there is two allocators implemented. +SizeOf returns the size of T in bytes + +
Example +

-### C allocator -The C allocator is using CGO underthehood to call calloc, realloc and free. ```go -alloc := allocator.NewC() -defer alloc.Destroy() +fmt.Println(mm.SizeOf[int32]()) +fmt.Println(mm.SizeOf[int64]()) +// Output: +// 4 +// 8 +``` -ptr := allocator.Alloc[int](alloc) -defer allocator.Free(alloc, ptr) +#### Output -*ptr = 15 +``` +4 +8 +``` + +

+
+ +# allocator + +```go +import "github.com/joetifa2003/mm-go/allocator" ``` -### BatchAllocator +## Index -This allocator purpose is to reduce the overhead of calling CGO on every allocation/free, it also acts as an arena since it frees all the memory when `Destroy` is called. +- [func Alloc\[T any\]\(a Allocator\) \*T](<#Alloc>) +- [func AllocMany\[T any\]\(a Allocator, n int\) \[\]T](<#AllocMany>) +- [func Free\[T any\]\(a Allocator, ptr \*T\)](<#Free>) +- [func FreeMany\[T any\]\(a Allocator, slice \[\]T\)](<#FreeMany>) +- [func Realloc\[T any\]\(a Allocator, slice \[\]T, newN int\) \[\]T](<#Realloc>) +- [type Allocator](<#Allocator>) + - [func NewAllocator\(allocator unsafe.Pointer, alloc func\(allocator unsafe.Pointer, size int\) unsafe.Pointer, free func\(allocator unsafe.Pointer, ptr unsafe.Pointer\), realloc func\(allocator unsafe.Pointer, ptr unsafe.Pointer, size int\) unsafe.Pointer, destroy func\(allocator unsafe.Pointer\)\) Allocator](<#NewAllocator>) + - [func NewC\(\) Allocator](<#NewC>) + - [func \(a Allocator\) Alloc\(size int\) unsafe.Pointer](<#Allocator.Alloc>) + - [func \(a Allocator\) Destroy\(\)](<#Allocator.Destroy>) + - [func \(a Allocator\) Free\(ptr unsafe.Pointer\)](<#Allocator.Free>) + - [func \(a Allocator\) Realloc\(ptr unsafe.Pointer, size int\) unsafe.Pointer](<#Allocator.Realloc>) -Instead, it allocats large chunks of memory at once and then divides them when you allocate, making it much faster. -This allocator has to take another allocator for it to work, usually with the C allocator. + +## func [Alloc]() ```go -alloc := batchallocator.New(allocator.NewC()) -defer alloc.Destroy() +func Alloc[T any](a Allocator) *T +``` + +Alloc allocates T and returns a pointer to it. + +
Example +

+ -ptr := allocator.Alloc[int](alloc) -defer allocator.Free(alloc, ptr) +```go +alloc := allocator.NewC() +defer alloc.Destroy() + +// So you can do this: +ptr := allocator.Alloc[int](alloc) // allocates a single int and returns a ptr to it +defer allocator.Free(alloc, ptr) // frees the int (defer recommended to prevent leaks) *ptr = 15 +fmt.Println(*ptr) + +// instead of doing this: +ptr2 := (*int)(alloc.Alloc(mm.SizeOf[int]())) +defer alloc.Free(unsafe.Pointer(ptr2)) +*ptr2 = 15 + +fmt.Println(*ptr2) + +// Output: +// 15 +// 15 +``` + +#### Output + +``` +15 +15 +``` + +

+
+ + +## func [AllocMany]() + +```go +func AllocMany[T any](a Allocator, n int) []T ``` -With this allocator, calling `Free/FreeMany` on pointers allocated with `Alloc/AllocMany` is optional, since when you call `Destroy` all memory is freed by default. +AllocMany allocates n of T and returns a slice representing the heap. CAUTION: don't append to the slice, the purpose of it is to replace pointer arithmetic with slice indexing + +
Example +

-But if you call `Free` the memory will be freed, so it acts as both Slab allocator and an Arena. -You can specify the size of chunks that are allocated by using options. ```go -alloc := batchallocator.New(allocator.NewC(), - batchallocator.WithBucketSize(mm.SizeOf[int]()*15), +package main + +import ( + "fmt" + + "github.com/joetifa2003/mm-go/allocator" ) + +func main() { + alloc := allocator.NewC() + defer alloc.Destroy() + + heap := allocator.AllocMany[int](alloc, 2) // allocates 2 ints and returns it as a slice of ints with length 2 + defer allocator.FreeMany(alloc, heap) // it's recommended to make sure the data gets deallocated (defer recommended to prevent leaks) + + heap[0] = 15 // changes the data in the slice (aka the heap) + ptr := &heap[0] // takes a pointer to the first int in the heap + // Be careful if you do ptr := heap[0] this will take a copy from the data on the heap + *ptr = 45 // changes the value from 15 to 45 + heap[1] = 70 + + fmt.Println(heap[0]) + fmt.Println(heap[1]) + +} +``` + +#### Output + +``` +45 +70 ``` -For example this configures the batch allocator to allocate at minimum 15 ints at a time (by default it allocates ` page, which is usually 4kb). +

+
-You can also allocate more than this configured amount in one big allocation, and it will work fine, unlike `typedarena`, more on that later. + +## func [Free]() -### Generic Helpers +```go +func Free[T any](a Allocator, ptr *T) +``` -As you saw in the examples above, there are some helper functions that automatically detrimine the size of the type you want to allocate, and it also automatically does type casting from `unsafe.Pointer`. +FreeMany frees memory allocated by Alloc takes a ptr CAUTION: be careful not to double free, and prefer using defer to deallocate -So instead of doing this: + +## func [FreeMany]() ```go -alloc := batchallocator.New(allocator.NewC()) -defer alloc.Destroy() +func FreeMany[T any](a Allocator, slice []T) +``` + +FreeMany frees memory allocated by AllocMany takes in the slice \(aka the heap\) CAUTION: be careful not to double free, and prefer using defer to deallocate + + +## func [Realloc]() -ptr := (*int)(alloc.Alloc(int(unsafe.Sizeof(int)))) -defer alloc.Free(unsafe.Pointer(ptr)) +```go +func Realloc[T any](a Allocator, slice []T, newN int) []T ``` -You can do this: +Realloc reallocates memory allocated with AllocMany and doesn't change underling data + +
Example +

+ + ```go -alloc := batchallocator.New(allocator.NewC()) -defer alloc.Destroy() +package main -ptr := allocator.Alloc[int](alloc) -defer allocator.Free(alloc, ptr) +import ( + "fmt" + + "github.com/joetifa2003/mm-go/allocator" +) + +func main() { + alloc := allocator.NewC() + defer alloc.Destroy() + + heap := allocator.AllocMany[int](alloc, 2) // allocates 2 int and returns it as a slice of ints with length 2 + + heap[0] = 15 + heap[1] = 70 + + heap = allocator.Realloc(alloc, heap, 3) + allocator.FreeMany(alloc, heap) + + heap[3] = 100 + + fmt.Println(heap[0]) + fmt.Println(heap[1]) + fmt.Println(heap[3]) + +} +``` + +#### Output + +``` +15 +70 +100 ``` -Yes, go doesn't have generic on pointer receivers, so these had to be implemented as functions. +

+
-#### Alloc/Free + +## type [Allocator]() -Alloc is a generic function that allocates T and returns a pointer to it that you can free later using Free +Allocator is an interface that defines some methods needed for most allocators. It's not a golang interface, so it's safe to use in manually managed structs \(will not get garbage collected\). ```go -alloc := batchallocator.New(allocator.NewC()) -defer alloc.Destroy() +type Allocator struct { + // contains filtered or unexported fields +} +``` -ptr := allocator.Alloc[int](alloc) // allocates a single int and returns a ptr to it -defer allocator.Free(alloc, ptr) // frees the int (defer recommended to prevent leaks) + +### func [NewAllocator]() -assert.Equal(0, *ptr) // allocations are zeroed by default -*ptr = 15 // changes the value using the pointer -assert.Equal(15, *ptr) +```go +func NewAllocator(allocator unsafe.Pointer, alloc func(allocator unsafe.Pointer, size int) unsafe.Pointer, free func(allocator unsafe.Pointer, ptr unsafe.Pointer), realloc func(allocator unsafe.Pointer, ptr unsafe.Pointer, size int) unsafe.Pointer, destroy func(allocator unsafe.Pointer)) Allocator ``` +NewAllocator creates a new Allocator + +
Example +

+ + + ```go -type Node struct { - value int +package main + +import ( + "unsafe" + + "github.com/joetifa2003/mm-go/allocator" +) + +func main() { + // Create a custom allocator + alloc := allocator.NewAllocator( + nil, + myallocator_alloc, + myallocator_free, + myallocator_realloc, + myallocator_destroy, + ) + + // Check how C allocator is implemented + // or batchallocator soruce for a reference + + _ = alloc +} + +func myallocator_alloc(allocator unsafe.Pointer, size int) unsafe.Pointer { + return nil +} + +func myallocator_free(allocator unsafe.Pointer, ptr unsafe.Pointer) { +} + +func myallocator_realloc(allocator unsafe.Pointer, ptr unsafe.Pointer, size int) unsafe.Pointer { + return nil } -alloc := batchallocator.New(allocator.NewC()) -ptr := allocator.Alloc[Node](alloc) // allocates a single Node struct and returns a ptr to it -defer allocator.Free(alloc, ptr) // frees the struct (defer recommended to prevent leaks) +func myallocator_destroy(allocator unsafe.Pointer) { +} ``` -#### AllocMany/FreeMany +

+
-AllocMany is a generic function that allocates n of T and returns a slice that represents the heap (instead of pointer arithmetic => slice indexing) that you can free later using FreeMany + +### func [NewC]() ```go -alloc := allocator.NewC() -defer allocator.Destroy() +func NewC() Allocator +``` -heap := allocator.AllocMany[int](alloc, 2) // allocates 2 ints and returns it as a slice of ints with length 2 -defer allocator.FreeMany(heap) // it's recommended to make sure the data gets deallocated (defer recommended to prevent leaks) +NewC returns an allocator that uses C calloc, realloc and free. -assert.Equal(2, len(heap)) -heap[0] = 15 // changes the data in the slice (aka the heap) -ptr := &heap[0] // takes a pointer to the first int in the heap -// Be careful if you do ptr := heap[0] this will take a copy from the data on the heap -*ptr = 45 // changes the value from 15 to 45 +
Example +

-assert.Equal(45, heap[0]) -assert.Equal(0, heap[1]) + + +```go +package main + +import ( + "fmt" + + "github.com/joetifa2003/mm-go/allocator" +) + +func main() { + alloc := allocator.NewC() + defer alloc.Destroy() + + ptr := allocator.Alloc[int](alloc) + defer allocator.Free(alloc, ptr) + + *ptr = 15 + fmt.Println(*ptr) + +} ``` -WARNING: Do not append to the slice, this is only used to avoid pointer arithmetic and unsafe code. +#### Output + +``` +15 +``` -### ReAlloc +

+
-Reallocate reallocates memory allocated with AllocMany and doesn't change underling data + +### func \(Allocator\) [Alloc]() ```go -alloc := allocator.NewC() -defer alloc.Destroy() +func (a Allocator) Alloc(size int) unsafe.Pointer +``` + +Alloc allocates size bytes and returns an unsafe pointer to it. -heap := allocator.AllocMany[int](alloc, 2) // allocates 2 int and returns it as a slice of ints with length 2 -heap[0] = 15 -assert.Equal(2, len(heap)) + +### func \(Allocator\) [Destroy]() + +```go +func (a Allocator) Destroy() +``` -heap = allocator.Realloc(allocated, 3) +Destroy destroys the allocator. After calling this, the allocator is no longer usable. This is useful for cleanup, freeing allocator internal resources, etc. -assert.Equal(3, len(heap)) -assert.Equal(15, heap[0]) // data after reallocation stays the same + +### func \(Allocator\) [Free]() -allocator.FreeMany(heap) // didn't use defer here because i'm doing a reallocation and changing the value of allocated variable (otherwise can segfault) +```go +func (a Allocator) Free(ptr unsafe.Pointer) ``` +Free frees the memory pointed by ptr + + +### func \(Allocator\) [Realloc]() + +```go +func (a Allocator) Realloc(ptr unsafe.Pointer, size int) unsafe.Pointer +``` -## typedarena +Realloc reallocates the memory pointed by ptr with a new size and returns a new pointer to it. -New creates a typed arena with the specified chunk size. -a chunk is the the unit of the arena, if T is int for example and the -chunk size is 5, then each chunk is going to hold 5 ints. And if the -chunk is filled it will allocate another chunk that can hold 5 ints. -then you can call FreeArena and it will deallocate all chunks together. -Using this will simplify memory management. +# batchallocator ```go -alloc := allocator.NewC() -defer alloc.Destroy() +import "github.com/joetifa2003/mm-go/batchallocator" +``` + +This allocator purpose is to reduce the overhead of calling CGO on every allocation/free, it also acts as an arena since it frees all the memory when \`Destroy\` is called. It allocats large chunks of memory at once and then divides them when you allocate, making it much faster. This allocator has to take another allocator for it to work, usually with the C allocator. You can optionally call \`Free\` on the pointers allocated by batchallocator manually, and it will free the memory as soon as it can. \`Destroy\` must be called to free internal resources and free all the memory allocated by the allocator. + +
Example +

+ -arena := typedarena.New[int](alloc, 3) // 3 is the chunk size which gets preallocated, if you allocated more than 3 it will preallocate another chunk of 3 T -defer arena.Free() // freeing the arena using defer to prevent leaks -int1 := arena.Alloc() // allocates 1 int from arena -*int1 = 1 // changing it's value -ints := arena.AllocMany(2) // allocates 2 ints from the arena and returns a slice representing the heap (instead of pointer arithmetic) -ints[0] = 2 // changing the first value -ints[1] = 3 // changing the second value +```go +package main + +import ( + "github.com/joetifa2003/mm-go/allocator" + "github.com/joetifa2003/mm-go/batchallocator" +) -// you can also take pointers from the slice -intPtr1 := &ints[0] // taking pointer from the manually managed heap -*intPtr1 = 15 // changing the value using pointers +func main() { + alloc := batchallocator.New(allocator.NewC()) // by default it allocates page, which is usually 4kb + defer alloc.Destroy() // this frees all memory allocated by the allocator automatically -assert.Equal(1, *int1) -assert.Equal(2, len(ints)) -assert.Equal(15, ints[0]) -assert.Equal(3, ints[1]) + ptr := allocator.Alloc[int](alloc) + // but you can still free the pointers manually if you want (will free buckets of memory if all pointers depending on it is freed) + defer allocator.Free(alloc, ptr) // this can removed and the memory will be freed. +} ``` -### Why does this exists while there is BatchAllocator? +

+
-- `typedarena` is much faster because it only works with one single type. -- `batchallocator` is more generic and works for any type, even multiple types all at once. +## Index + +- [func New\(a allocator.Allocator, options ...BatchAllocatorOption\) allocator.Allocator](<#New>) +- [type BatchAllocator](<#BatchAllocator>) +- [type BatchAllocatorOption](<#BatchAllocatorOption>) + - [func WithBucketSize\(size int\) BatchAllocatorOption](<#WithBucketSize>) + + + +## func [New]() ```go -// You cannot do this with `typedarena` because it only works with one single type. +func New(a allocator.Allocator, options ...BatchAllocatorOption) allocator.Allocator +``` -alloc := batchallocator.New(allocator.NewC()) -defer alloc.Destroy() +New creates a new BatchAllocator and applies optional configuration using BatchAllocatorOption + + +## type [BatchAllocator]() -i := allocator.Alloc[int](alloc) -s := allocator.Alloc[string](alloc) -x := allocator.Alloc[float64](alloc) +BatchAllocator manages a collection of memory buckets to optimize small allocations -// they are all freed automatically because of Destroy above +```go +type BatchAllocator struct { + // contains filtered or unexported fields +} ``` -- `batchallocator` can be passed to multiple data structures, like `vector` and `hashmap` and it will be automatically Freed when `Destroy` is called. + +## type [BatchAllocatorOption]() + -- Also `typedarena.AllocMany` cannot exceed chunk size, but with `batchallocator` you can request any amount of memory. ```go -alloc := allocator.NewC() +type BatchAllocatorOption func(alloc *BatchAllocator) +``` + + +### func [WithBucketSize]() + +```go +func WithBucketSize(size int) BatchAllocatorOption +``` + +Option to specify bucket size when creating BatchAllocator + +
Example +

+ + + +```go +alloc := batchallocator.New( + allocator.NewC(), + batchallocator.WithBucketSize(mm.SizeOf[int]()*15), // configure the allocator to allocate size of 15 ints in one go. +) defer alloc.Destroy() -arena := typedarena.New[int](alloc, 3) +ptr := allocator.Alloc[int](alloc) +defer allocator.Free(alloc, ptr) // this can be removed and the memory will still be freed on Destroy. + +ptr2 := allocator.Alloc[int](alloc) // will not call CGO because there is still enough memory in the Bucket. +defer allocator.Free(alloc, ptr2) // this can be removed and the memory will still be freed on Destroy. +``` + +

+
-heap := arena.AllocMany(2) // fine -heap2 := arena.AllocMany(2) // also fine -heap2 := arena.AllocMany(5) // panics +# hashmap + +```go +import "github.com/joetifa2003/mm-go/hashmap" ``` +## Index -## vector +- [type Hashmap](<#Hashmap>) + - [func New\[K comparable, V any\]\(alloc allocator.Allocator\) \*Hashmap\[K, V\]](<#New>) + - [func \(hm \*Hashmap\[K, V\]\) Delete\(key K\)](<#Hashmap[K, V].Delete>) + - [func \(hm \*Hashmap\[K, V\]\) ForEach\(f func\(key K, value V\)\)](<#Hashmap[K, V].ForEach>) + - [func \(hm \*Hashmap\[K, V\]\) Free\(\)](<#Hashmap[K, V].Free>) + - [func \(hm \*Hashmap\[K, V\]\) Get\(key K\) \(value V, exists bool\)](<#Hashmap[K, V].Get>) + - [func \(hm \*Hashmap\[K, V\]\) GetPtr\(key K\) \(value \*V, exists bool\)](<#Hashmap[K, V].GetPtr>) + - [func \(hm \*Hashmap\[K, V\]\) Insert\(key K, value V\)](<#Hashmap[K, V].Insert>) + - [func \(hm \*Hashmap\[K, V\]\) Keys\(\) \[\]K](<#Hashmap[K, V].Keys>) + - [func \(hm \*Hashmap\[K, V\]\) Values\(\) \[\]V](<#Hashmap[K, V].Values>) -A contiguous growable array type. -You can think of the Vector as a manually managed slice that you can put in manually managed structs, if you put a slice in a manually managed struct it will get collected because go GC doesn't see the manually allocated struct. + + +## type [Hashmap]() + +Hashmap Manually managed hashmap, ```go -v := vector.New[int]() -defer v.Free() +type Hashmap[K comparable, V any] struct { + // contains filtered or unexported fields +} +``` -v.Push(1) -v.Push(2) -v.Push(3) + +### func [New]() -assert.Equal(3, v.Len()) -assert.Equal(4, v.Cap()) -assert.Equal([]int{1, 2, 3}, v.Slice()) -assert.Equal(3, v.Pop()) -assert.Equal(2, v.Pop()) -assert.Equal(1, v.Pop()) +```go +func New[K comparable, V any](alloc allocator.Allocator) *Hashmap[K, V] ``` +New creates a new Hashmap with key of type K and value of type V + + +### func \(\*Hashmap\[K, V\]\) [Delete]() + ```go -v := vector.New[int](5) -defer v.Free() +func (hm *Hashmap[K, V]) Delete(key K) +``` -assert.Equal(5, v.Len()) -assert.Equal(5, v.Cap()) +Delete delete value with key K + + +### func \(\*Hashmap\[K, V\]\) [ForEach]() + +```go +func (hm *Hashmap[K, V]) ForEach(f func(key K, value V)) ``` +ForEach iterates through all key/value pairs + + +### func \(\*Hashmap\[K, V\]\) [Free]() + ```go -v := vector.New[int](5, 6) -defer v.Free() +func (hm *Hashmap[K, V]) Free() +``` + +Free frees the Hashmap + + +### func \(\*Hashmap\[K, V\]\) [Get]() -assert.Equal(5, v.Len()) -assert.Equal(6, v.Cap()) +```go +func (hm *Hashmap[K, V]) Get(key K) (value V, exists bool) ``` +Get takes key K and return value V + + +### func \(\*Hashmap\[K, V\]\) [GetPtr]() + ```go -v := vector.Init(1, 2, 3) -defer v.Free() +func (hm *Hashmap[K, V]) GetPtr(key K) (value *V, exists bool) +``` -assert.Equal(3, v.Len()) -assert.Equal(3, v.Cap()) +GetPtr takes key K and return a pointer to value V -assert.Equal(3, v.Pop()) -assert.Equal(2, v.Pop()) -assert.Equal(1, v.Pop()) + +### func \(\*Hashmap\[K, V\]\) [Insert]() + +```go +func (hm *Hashmap[K, V]) Insert(key K, value V) ``` +Insert inserts a new value V if key K doesn't exist, Otherwise update the key K with value V -## Benchmarks + +### func \(\*Hashmap\[K, V\]\) [Keys]() -Check the test files and github actions for the benchmarks (linux, macos, windows). -mm-go can sometimes be 5-10 times faster. +```go +func (hm *Hashmap[K, V]) Keys() []K +``` + +Keys returns all keys as a slice + +### func \(\*Hashmap\[K, V\]\) [Values]() + +```go +func (hm *Hashmap[K, V]) Values() []V ``` -Run go test ./... -bench=. -count 5 > out.txt && benchstat out.txt -name time/op -pkg:github.com/joetifa2003/mm-go goos:linux goarch:amd64 -HeapManaged/node_count_10000-2 504µs ± 1% -HeapManaged/node_count_100000-2 3.73ms ± 6% -HeapManaged/node_count_10000000-2 664ms ± 8% -HeapManaged/node_count_100000000-2 6.30s ± 4% -Manual/node_count_10000-2 226µs ± 1% -Manual/node_count_100000-2 576µs ± 1% -Manual/node_count_10000000-2 70.6ms ± 1% -Manual/node_count_100000000-2 702ms ± 1% -ArenaManual/node_count_10000-2 226µs ± 1% -ArenaManual/node_count_100000-2 553µs ± 0% -ArenaManual/node_count_10000000-2 69.1ms ± 0% -ArenaManual/node_count_100000000-2 681ms ± 1% -BinaryTreeManaged-2 6.07s ±10% -BinaryTreeArena/chunk_size_50-2 2.30s ±21% -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% +Values returns all values as a slice + +# linkedlist + +```go +import "github.com/joetifa2003/mm-go/linkedlist" +``` + +## Index + +- [type LinkedList](<#LinkedList>) + - [func New\[T any\]\(alloc allocator.Allocator\) \*LinkedList\[T\]](<#New>) + - [func \(ll \*LinkedList\[T\]\) At\(idx int\) T](<#LinkedList[T].At>) + - [func \(ll \*LinkedList\[T\]\) AtPtr\(idx int\) \*T](<#LinkedList[T].AtPtr>) + - [func \(ll \*LinkedList\[T\]\) FindIndex\(f func\(value T\) bool\) \(idx int, ok bool\)](<#LinkedList[T].FindIndex>) + - [func \(ll \*LinkedList\[T\]\) FindIndexes\(f func\(value T\) bool\) \[\]int](<#LinkedList[T].FindIndexes>) + - [func \(ll \*LinkedList\[T\]\) ForEach\(f func\(idx int, value T\)\)](<#LinkedList[T].ForEach>) + - [func \(ll \*LinkedList\[T\]\) Free\(\)](<#LinkedList[T].Free>) + - [func \(ll \*LinkedList\[T\]\) Iter\(\) iter.Seq\[T\]](<#LinkedList[T].Iter>) + - [func \(ll \*LinkedList\[T\]\) Len\(\) int](<#LinkedList[T].Len>) + - [func \(ll \*LinkedList\[T\]\) PopBack\(\) T](<#LinkedList[T].PopBack>) + - [func \(ll \*LinkedList\[T\]\) PopFront\(\) T](<#LinkedList[T].PopFront>) + - [func \(ll \*LinkedList\[T\]\) PushBack\(value T\)](<#LinkedList[T].PushBack>) + - [func \(ll \*LinkedList\[T\]\) PushFront\(value T\)](<#LinkedList[T].PushFront>) + - [func \(ll \*LinkedList\[T\]\) Remove\(f func\(idx int, value T\) bool\) \(value T, ok bool\)](<#LinkedList[T].Remove>) + - [func \(ll \*LinkedList\[T\]\) RemoveAll\(f func\(idx int, value T\) bool\) \[\]T](<#LinkedList[T].RemoveAll>) + - [func \(ll \*LinkedList\[T\]\) RemoveAt\(idx int\) T](<#LinkedList[T].RemoveAt>) + + + +## type [LinkedList]() + +LinkedList a doubly\-linked list. Note: can be a lot slower than Vector but sometimes faster in specific use cases + +```go +type LinkedList[T any] struct { + // contains filtered or unexported fields +} ``` + + +### func [New]() + +```go +func New[T any](alloc allocator.Allocator) *LinkedList[T] +``` + +New creates a new linked list. + + +### func \(\*LinkedList\[T\]\) [At]() + +```go +func (ll *LinkedList[T]) At(idx int) T +``` + +At gets value T at idx. + + +### func \(\*LinkedList\[T\]\) [AtPtr]() + +```go +func (ll *LinkedList[T]) AtPtr(idx int) *T +``` + +AtPtr gets a pointer to value T at idx. + + +### func \(\*LinkedList\[T\]\) [FindIndex]() + +```go +func (ll *LinkedList[T]) FindIndex(f func(value T) bool) (idx int, ok bool) +``` + +FindIndex returns the first index of value T that pass the test implemented by the provided function. + + +### func \(\*LinkedList\[T\]\) [FindIndexes]() + +```go +func (ll *LinkedList[T]) FindIndexes(f func(value T) bool) []int +``` + +FindIndex returns all indexes of value T that pass the test implemented by the provided function. + + +### func \(\*LinkedList\[T\]\) [ForEach]() + +```go +func (ll *LinkedList[T]) ForEach(f func(idx int, value T)) +``` + +ForEach iterates through the linked list. + + +### func \(\*LinkedList\[T\]\) [Free]() + +```go +func (ll *LinkedList[T]) Free() +``` + +Free frees the linked list. + + +### func \(\*LinkedList\[T\]\) [Iter]() + +```go +func (ll *LinkedList[T]) Iter() iter.Seq[T] +``` + + + + +### func \(\*LinkedList\[T\]\) [Len]() + +```go +func (ll *LinkedList[T]) Len() int +``` + +Len gets linked list length. + + +### func \(\*LinkedList\[T\]\) [PopBack]() + +```go +func (ll *LinkedList[T]) PopBack() T +``` + +PopBack pops and returns value T from the back of the linked list. + + +### func \(\*LinkedList\[T\]\) [PopFront]() + +```go +func (ll *LinkedList[T]) PopFront() T +``` + +PopFront pops and returns value T from the front of the linked list. + + +### func \(\*LinkedList\[T\]\) [PushBack]() + +```go +func (ll *LinkedList[T]) PushBack(value T) +``` + +PushBack pushes value T to the back of the linked list. + + +### func \(\*LinkedList\[T\]\) [PushFront]() + +```go +func (ll *LinkedList[T]) PushFront(value T) +``` + +PushFront pushes value T to the back of the linked list. + + +### func \(\*LinkedList\[T\]\) [Remove]() + +```go +func (ll *LinkedList[T]) Remove(f func(idx int, value T) bool) (value T, ok bool) +``` + +Remove removes the first value T that pass the test implemented by the provided function. if the test succeeded it will return the value and true + + +### func \(\*LinkedList\[T\]\) [RemoveAll]() + +```go +func (ll *LinkedList[T]) RemoveAll(f func(idx int, value T) bool) []T +``` + +RemoveAll removes all values of T that pass the test implemented by the provided function. + + +### func \(\*LinkedList\[T\]\) [RemoveAt]() + +```go +func (ll *LinkedList[T]) RemoveAt(idx int) T +``` + +RemoveAt removes value T at specified index and returns it. + +# minheap + +```go +import "github.com/joetifa2003/mm-go/minheap" +``` + +## Index + +- [type MinHeap](<#MinHeap>) + - [func New\[T any\]\(alloc allocator.Allocator, less func\(a, b T\) bool\) \*MinHeap\[T\]](<#New>) + - [func \(h \*MinHeap\[T\]\) Free\(\)](<#MinHeap[T].Free>) + - [func \(h \*MinHeap\[T\]\) Iter\(\) iter.Seq\[T\]](<#MinHeap[T].Iter>) + - [func \(h \*MinHeap\[T\]\) Len\(\) int](<#MinHeap[T].Len>) + - [func \(h \*MinHeap\[T\]\) Peek\(\) T](<#MinHeap[T].Peek>) + - [func \(h \*MinHeap\[T\]\) Pop\(\) T](<#MinHeap[T].Pop>) + - [func \(h \*MinHeap\[T\]\) Push\(value T\)](<#MinHeap[T].Push>) + - [func \(h \*MinHeap\[T\]\) Remove\(f func\(T\) bool\)](<#MinHeap[T].Remove>) + + + +## type [MinHeap]() + + + +```go +type MinHeap[T any] struct { + // contains filtered or unexported fields +} +``` + + +### func [New]() + +```go +func New[T any](alloc allocator.Allocator, less func(a, b T) bool) *MinHeap[T] +``` + +New creates a new MinHeap. + + +### func \(\*MinHeap\[T\]\) [Free]() + +```go +func (h *MinHeap[T]) Free() +``` + +Free frees the heap. + + +### func \(\*MinHeap\[T\]\) [Iter]() + +```go +func (h *MinHeap[T]) Iter() iter.Seq[T] +``` + + + + +### func \(\*MinHeap\[T\]\) [Len]() + +```go +func (h *MinHeap[T]) Len() int +``` + +Len returns the number of elements in the heap. + + +### func \(\*MinHeap\[T\]\) [Peek]() + +```go +func (h *MinHeap[T]) Peek() T +``` + +Peek returns the minimum value from the heap without removing it. + + +### func \(\*MinHeap\[T\]\) [Pop]() + +```go +func (h *MinHeap[T]) Pop() T +``` + +Pop removes and returns the minimum value from the heap. + + +### func \(\*MinHeap\[T\]\) [Push]() + +```go +func (h *MinHeap[T]) Push(value T) +``` + +Push adds a value to the heap. + + +### func \(\*MinHeap\[T\]\) [Remove]() + +```go +func (h *MinHeap[T]) Remove(f func(T) bool) +``` + + + +# mmstring + +```go +import "github.com/joetifa2003/mm-go/mmstring" +``` + +## Index + +- [type MMString](<#MMString>) + - [func From\(alloc allocator.Allocator, input string\) \*MMString](<#From>) + - [func New\(alloc allocator.Allocator\) \*MMString](<#New>) + - [func \(s \*MMString\) AppendGoString\(input string\)](<#MMString.AppendGoString>) + - [func \(s \*MMString\) Free\(\)](<#MMString.Free>) + - [func \(s \*MMString\) GetGoString\(\) string](<#MMString.GetGoString>) + + + +## type [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 { + // contains filtered or unexported fields +} +``` + + +### func [From]() + +```go +func From(alloc allocator.Allocator, input string) *MMString +``` + +From creates a new manually managed string, And initialize it with a go string + + +### func [New]() + +```go +func New(alloc allocator.Allocator) *MMString +``` + +New create a new manually managed string + + +### func \(\*MMString\) [AppendGoString]() + +```go +func (s *MMString) AppendGoString(input string) +``` + +AppendGoString appends go string to manually managed string + + +### func \(\*MMString\) [Free]() + +```go +func (s *MMString) Free() +``` + +Free frees MMString + + +### func \(\*MMString\) [GetGoString]() + +```go +func (s *MMString) GetGoString() string +``` + +GetGoString returns go string from manually managed string. CAUTION: You also have to free the MMString + +# typedarena + +```go +import "github.com/joetifa2003/mm-go/typedarena" +``` + +## Index + +- [type TypedArena](<#TypedArena>) + - [func New\[T any\]\(alloc allocator.Allocator, chunkSize int\) \*TypedArena\[T\]](<#New>) + - [func \(ta \*TypedArena\[T\]\) Alloc\(\) \*T](<#TypedArena[T].Alloc>) + - [func \(ta \*TypedArena\[T\]\) AllocMany\(n int\) \[\]T](<#TypedArena[T].AllocMany>) + - [func \(ta \*TypedArena\[T\]\) Free\(\)](<#TypedArena[T].Free>) + + + +## type [TypedArena]() + +TypedArena is a growable typed arena + +```go +type TypedArena[T any] struct { + // contains filtered or unexported fields +} +``` + + +### func [New]() + +```go +func New[T any](alloc allocator.Allocator, chunkSize int) *TypedArena[T] +``` + +New creates a typed arena with the specified chunk size. a chunk is the the unit of the arena, if T is int for example and the chunk size is 5, then each chunk is going to hold 5 ints. And if the chunk is filled it will allocate another chunk that can hold 5 ints. then you can call FreeArena and it will deallocate all chunks together + + +### func \(\*TypedArena\[T\]\) [Alloc]() + +```go +func (ta *TypedArena[T]) Alloc() *T +``` + +Alloc allocates T from the arena + + +### func \(\*TypedArena\[T\]\) [AllocMany]() + +```go +func (ta *TypedArena[T]) AllocMany(n int) []T +``` + +AllocMany allocates n of T and returns a slice representing the heap. CAUTION: don't append to the slice, the purpose of it is to replace pointer arithmetic with slice indexing CAUTION: n cannot exceed chunk size + + +### func \(\*TypedArena\[T\]\) [Free]() + +```go +func (ta *TypedArena[T]) Free() +``` + +Free frees all allocated memory + +# vector + +```go +import "github.com/joetifa2003/mm-go/vector" +``` + +## Index + +- [type Vector](<#Vector>) + - [func Init\[T any\]\(alloc allocator.Allocator, values ...T\) \*Vector\[T\]](<#Init>) + - [func New\[T any\]\(aloc allocator.Allocator, args ...int\) \*Vector\[T\]](<#New>) + - [func \(v \*Vector\[T\]\) At\(idx int\) T](<#Vector[T].At>) + - [func \(v \*Vector\[T\]\) AtPtr\(idx int\) \*T](<#Vector[T].AtPtr>) + - [func \(v \*Vector\[T\]\) Cap\(\) int](<#Vector[T].Cap>) + - [func \(v \*Vector\[T\]\) Free\(\)](<#Vector[T].Free>) + - [func \(v \*Vector\[T\]\) Iter\(\) iter.Seq\[T\]](<#Vector[T].Iter>) + - [func \(v \*Vector\[T\]\) Last\(\) T](<#Vector[T].Last>) + - [func \(v \*Vector\[T\]\) Len\(\) int](<#Vector[T].Len>) + - [func \(v \*Vector\[T\]\) Pop\(\) T](<#Vector[T].Pop>) + - [func \(v \*Vector\[T\]\) Push\(value T\)](<#Vector[T].Push>) + - [func \(v \*Vector\[T\]\) RemoveAt\(idx int\) T](<#Vector[T].RemoveAt>) + - [func \(v \*Vector\[T\]\) Set\(idx int, value T\)](<#Vector[T].Set>) + - [func \(v \*Vector\[T\]\) Slice\(\) \[\]T](<#Vector[T].Slice>) + - [func \(v \*Vector\[T\]\) UnsafeAt\(idx int\) T](<#Vector[T].UnsafeAt>) + + + +## type [Vector]() + +Vector a contiguous growable array type + +```go +type Vector[T any] struct { + // contains filtered or unexported fields +} +``` + + +### func [Init]() + +```go +func Init[T any](alloc allocator.Allocator, values ...T) *Vector[T] +``` + +Init initializes a new vector with the T elements provided and sets it's len and cap to len\(values\) + + +### func [New]() + +```go +func New[T any](aloc allocator.Allocator, args ...int) *Vector[T] +``` + +New creates a new empty vector, if args not provided it will create an empty vector, if only one arg is provided it will init a vector with len and cap equal to the provided arg, if two args are provided it will init a vector with len = args\[0\] cap = args\[1\] + + +### func \(\*Vector\[T\]\) [At]() + +```go +func (v *Vector[T]) At(idx int) T +``` + +At gets element T at specified index + + +### func \(\*Vector\[T\]\) [AtPtr]() + +```go +func (v *Vector[T]) AtPtr(idx int) *T +``` + +AtPtr gets element a pointer of T at specified index + + +### func \(\*Vector\[T\]\) [Cap]() + +```go +func (v *Vector[T]) Cap() int +``` + +Cap gets vector capacity \(underling memory length\). + + +### func \(\*Vector\[T\]\) [Free]() + +```go +func (v *Vector[T]) Free() +``` + +Free deallocats the vector + + +### func \(\*Vector\[T\]\) [Iter]() + +```go +func (v *Vector[T]) Iter() iter.Seq[T] +``` + + + + +### func \(\*Vector\[T\]\) [Last]() + +```go +func (v *Vector[T]) Last() T +``` + +Last gets the last element from a vector + + +### func \(\*Vector\[T\]\) [Len]() + +```go +func (v *Vector[T]) Len() int +``` + +Len gets vector length + + +### func \(\*Vector\[T\]\) [Pop]() + +```go +func (v *Vector[T]) Pop() T +``` + +Pop pops value T from the vector and returns it + + +### func \(\*Vector\[T\]\) [Push]() + +```go +func (v *Vector[T]) Push(value T) +``` + +Push pushes value T to the vector, grows if needed. + + +### func \(\*Vector\[T\]\) [RemoveAt]() + +```go +func (v *Vector[T]) RemoveAt(idx int) T +``` + + + + +### func \(\*Vector\[T\]\) [Set]() + +```go +func (v *Vector[T]) Set(idx int, value T) +``` + +Set sets element T at specified index + + +### func \(\*Vector\[T\]\) [Slice]() + +```go +func (v *Vector[T]) Slice() []T +``` + +Slice gets a slice representing the vector CAUTION: don't append to this slice, this is only used if you want to loop on the vec elements + + +### func \(\*Vector\[T\]\) [UnsafeAt]() + +```go +func (v *Vector[T]) UnsafeAt(idx int) T +``` + +UnsafeAT gets element T at specified index without bounds checking + +Generated by [gomarkdoc]() + + + + + diff --git a/allocator/allocator.go b/allocator/allocator.go index 590ef27..9914a69 100644 --- a/allocator/allocator.go +++ b/allocator/allocator.go @@ -2,6 +2,8 @@ package allocator import "unsafe" +// Allocator is an interface that defines some methods needed for most allocators. +// It's not a golang interface, so it's safe to use in manually managed structs (will not get garbage collected). type Allocator struct { allocator unsafe.Pointer alloc func(allocator unsafe.Pointer, size int) unsafe.Pointer @@ -10,6 +12,7 @@ type Allocator struct { destroy func(allocator unsafe.Pointer) } +// NewAllocator creates a new Allocator func NewAllocator( allocator unsafe.Pointer, alloc func(allocator unsafe.Pointer, size int) unsafe.Pointer, @@ -26,18 +29,24 @@ func NewAllocator( } } +// Alloc allocates size bytes and returns an unsafe pointer to it. func (a Allocator) Alloc(size int) unsafe.Pointer { return a.alloc(a.allocator, size) } +// Free frees the memory pointed by ptr func (a Allocator) Free(ptr unsafe.Pointer) { a.free(a.allocator, ptr) } +// Realloc reallocates the memory pointed by ptr with a new size and returns a new pointer to it. func (a Allocator) Realloc(ptr unsafe.Pointer, size int) unsafe.Pointer { return a.realloc(a.allocator, ptr, size) } +// Destroy destroys the allocator. +// After calling this, the allocator is no longer usable. +// This is useful for cleanup, freeing allocator internal resources, etc. func (a Allocator) Destroy() { a.destroy(a.allocator) } diff --git a/allocator/callocator.go b/allocator/callocator.go index 7eacc2b..516cf75 100644 --- a/allocator/callocator.go +++ b/allocator/callocator.go @@ -5,6 +5,7 @@ import "C" import "unsafe" +// NewC returns an allocator that uses C calloc, realloc and free. func NewC() Allocator { return NewAllocator(nil, callocator_alloc, callocator_free, callocator_realloc, callocator_destroy) } diff --git a/allocator/example_test.go b/allocator/example_test.go new file mode 100644 index 0000000..13efd3b --- /dev/null +++ b/allocator/example_test.go @@ -0,0 +1,119 @@ +package allocator_test + +import ( + "fmt" + "unsafe" + + "github.com/joetifa2003/mm-go" + "github.com/joetifa2003/mm-go/allocator" +) + +func ExampleNewC() { + alloc := allocator.NewC() + defer alloc.Destroy() + + ptr := allocator.Alloc[int](alloc) + defer allocator.Free(alloc, ptr) + + *ptr = 15 + fmt.Println(*ptr) + + // Output: 15 +} + +func ExampleAlloc() { + alloc := allocator.NewC() + defer alloc.Destroy() + + // So you can do this: + ptr := allocator.Alloc[int](alloc) // allocates a single int and returns a ptr to it + defer allocator.Free(alloc, ptr) // frees the int (defer recommended to prevent leaks) + *ptr = 15 + fmt.Println(*ptr) + + // instead of doing this: + ptr2 := (*int)(alloc.Alloc(mm.SizeOf[int]())) + defer alloc.Free(unsafe.Pointer(ptr2)) + *ptr2 = 15 + + fmt.Println(*ptr2) + + // Output: + // 15 + // 15 +} + +func ExampleAllocMany() { + alloc := allocator.NewC() + defer alloc.Destroy() + + heap := allocator.AllocMany[int](alloc, 2) // allocates 2 ints and returns it as a slice of ints with length 2 + defer allocator.FreeMany(alloc, heap) // it's recommended to make sure the data gets deallocated (defer recommended to prevent leaks) + + heap[0] = 15 // changes the data in the slice (aka the heap) + ptr := &heap[0] // takes a pointer to the first int in the heap + // Be careful if you do ptr := heap[0] this will take a copy from the data on the heap + *ptr = 45 // changes the value from 15 to 45 + heap[1] = 70 + + fmt.Println(heap[0]) + fmt.Println(heap[1]) + + // Output: + // 45 + // 70 +} + +func ExampleRealloc() { + alloc := allocator.NewC() + defer alloc.Destroy() + + heap := allocator.AllocMany[int](alloc, 2) // allocates 2 int and returns it as a slice of ints with length 2 + + heap[0] = 15 + heap[1] = 70 + + heap = allocator.Realloc(alloc, heap, 3) + allocator.FreeMany(alloc, heap) + + heap[3] = 100 + + fmt.Println(heap[0]) + fmt.Println(heap[1]) + fmt.Println(heap[3]) + + // Output: + // 15 + // 70 + // 100 +} + +func ExampleNewAllocator() { + // Create a custom allocator + alloc := allocator.NewAllocator( + nil, + myallocator_alloc, + myallocator_free, + myallocator_realloc, + myallocator_destroy, + ) + + // Check how C allocator is implemented + // or batchallocator soruce for a reference + + _ = alloc +} + +func myallocator_alloc(allocator unsafe.Pointer, size int) unsafe.Pointer { + return nil +} + +func myallocator_free(allocator unsafe.Pointer, ptr unsafe.Pointer) { +} + +func myallocator_realloc(allocator unsafe.Pointer, ptr unsafe.Pointer, size int) unsafe.Pointer { + return nil +} + +func myallocator_destroy(allocator unsafe.Pointer) { +} diff --git a/batchallocator/batch.go b/batchallocator/batch.go index 803a011..1840433 100644 --- a/batchallocator/batch.go +++ b/batchallocator/batch.go @@ -1,3 +1,8 @@ +// This allocator purpose is to reduce the overhead of calling CGO on every allocation/free, it also acts as an arena since it frees all the memory when `Destroy` is called. +// It allocats large chunks of memory at once and then divides them when you allocate, making it much faster. +// This allocator has to take another allocator for it to work, usually with the C allocator. +// You can optionally call `Free` on the pointers allocated by batchallocator manually, and it will free the memory as soon as it can. +// `Destroy` must be called to free internal resources and free all the memory allocated by the allocator. package batchallocator import ( diff --git a/batchallocator/example_test.go b/batchallocator/example_test.go new file mode 100644 index 0000000..9bb7b11 --- /dev/null +++ b/batchallocator/example_test.go @@ -0,0 +1,31 @@ +package batchallocator_test + +import ( + "github.com/joetifa2003/mm-go" + "github.com/joetifa2003/mm-go/allocator" + "github.com/joetifa2003/mm-go/batchallocator" +) + +func Example() { + alloc := batchallocator.New(allocator.NewC()) // by default it allocates page, which is usually 4kb + defer alloc.Destroy() // this frees all memory allocated by the allocator automatically + + ptr := allocator.Alloc[int](alloc) + // but you can still free the pointers manually if you want (will free buckets of memory if all pointers depending on it is freed) + defer allocator.Free(alloc, ptr) // this can removed and the memory will be freed. +} + +func ExampleWithBucketSize() { + alloc := batchallocator.New( + allocator.NewC(), + batchallocator.WithBucketSize(mm.SizeOf[int]()*15), // configure the allocator to allocate size of 15 ints in one go. + ) + defer alloc.Destroy() + + ptr := allocator.Alloc[int](alloc) + defer allocator.Free(alloc, ptr) // this can be removed and the memory will still be freed on Destroy. + + ptr2 := allocator.Alloc[int](alloc) // will not call CGO because there is still enough memory in the Bucket. + defer allocator.Free(alloc, ptr2) // this can be removed and the memory will still be freed on Destroy. + +} diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..7eb0355 --- /dev/null +++ b/example_test.go @@ -0,0 +1,15 @@ +package mm_test + +import ( + "fmt" + + "github.com/joetifa2003/mm-go" +) + +func ExampleSizeOf() { + fmt.Println(mm.SizeOf[int32]()) + fmt.Println(mm.SizeOf[int64]()) + // Output: + // 4 + // 8 +} diff --git a/mm.go b/mm.go index bf18990..212b703 100644 --- a/mm.go +++ b/mm.go @@ -2,6 +2,7 @@ package mm import "unsafe" +// SizeOf returns the size of T in bytes func SizeOf[T any]() int { var zeroV T return int(unsafe.Sizeof(zeroV))