offheap

package
v0.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 13, 2024 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Usage

The offheap package allows us to allocate, free and retrieve Go objects manually. The objects allocated in this way are not visible to the garbage collector, allowing us to build very large datastructures like trees and hashmaps without impacting garbage collection efficiency.

Each Store instance can allocate/free a wide variety of object types. Broadly we can allocate _any_ Go type so long as no part of the type contains pointers. We can allocate slices of pointerless types with a dedicated slice type. And finally we can allocate strings through a dedicated string type.

Each allocation has a corresponding Reference, named RefObject, RefSlice and RefString respectively, which acts like a conventional pointer to retrieve the allocated object via Reference.Value() e.g.

var store *offheap.Store = offheap.New()

var ref offheap.RefObject[int] = offheap.AllocObject[int](store)
var i1 *int = ref.Value()

When you know that an allocation will never be used again it's memory can be released back to the Store using one of the Free*() functions e.g.

var store *offheap.Store = offheap.New()

var ref offheap.RefObject[int] = offheap.AllocObject[int](store)

offheap.FreeObject(store, ref)
// You must never use ref again

A best effort has been made to panic if an object is freed twice or if a freed object is accessed using Reference.Value(). However, it isn't guaranteed that these calls will panic.

References can be kept and stored in arbitrary datastructures, which can themselves be managed by a Store e.g.

type Node struct {
	left  offheap.RefObject[Node]
	right offheap.RefObject[Node]
}

var store *offheap.Store = offheap.New()

var refParent offheap.RefObject[Node] = offheap.AllocObject[Node]((store))

The Reference types contain no conventional Go pointers which are recognised by the garbage collector.

It is important to note that the objects managed by a Store do not exist on the managed Go heap. They live in a series of manually mapped memory regions which are managed separately by the Store. This means that the amount of memory used by the Store has no impact on the frequency of garbage collection runs.

Not all pointers in Go types are obvious. Here are a few examples of types which can't be managed in a Store.

type BadStruct1 struct {
  stringsHavePointers string
}

type BadStruct2 struct {
  mapsHavePointers map[int]int
}

type BadStruct3 struct {
  slicesHavePointers []int
}

type BadStruct4 struct {
  pointersHavePointers *int
}

type BadStruct5 struct {
  storesHavePointers *Store
}

Trying to allocate an object or slice with a generic type which contains pointers will panic.

Memory Model Constraints:

A Store has a moderate degree of concurrency safety, but users must still be careful how they access and modify data allocated by a Store instance. A shorter version of the guarantees described below would be to say that allocating and retrieving objects managed by a Store has the same guarantees and limitations that conventionally allocated Go objects have.

Concurrency Guarantees

1: Independent Alloc/Free Safety

It is safe for multiple goroutines using a shared Store instance to call Alloc() and Free() generating independent sets of objects/References. They can safely read the objects they have allocated without any additional concurrency protection.

2: Safe Data Publication

It is safe to create objects using Alloc() and then share those objects with other goroutines. We must establish the usual happens-before relationships when sharing objects/References with other goroutines.

For example it is safe to Alloc() new objects and publish References to those objects on a channel and have other goroutines read from that channel and call Reference.Value() on those References.

3: Independent Read Safety

For a given set of live objects, previously allocated with a happens-before barrier between the allocator and readers, all objects can be read freely. Calling Reference.Value() and performing arbitrary reads of the retrieved objects from multiple goroutines with no other concurrency control code will work without data races.

4: Safe Object Reads And Writes

It is not safe for multiple goroutines to freely write to the object, nor to have multiple goroutines freely perform a mixture of read/write operations on the object. You can however perform concurrent reads and writes to a shared object if you use appropriate concurrency controls such as sync.Mutex.

5: Free Safety

If we call Free() on the same Reference (a Reference pointing to the same allocation) concurrently from two or more goroutines this will be a data race. The behaviour is unpredictable in this case. This is also a bug, but potentially one with stranger behaviour than just calling Free() twice from a single goroutine.

If we call Free() on a Reference while another goroutine is calling Reference.Value() this is a data race. This will have unpredictable behaviour, and is never safe.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ConfForSlice

func ConfForSlice[T any](s *Store, capacity int) pointerstore.AllocConfig

Returns the allocation config for the allocation size of a []T with capacity.

It is important to note that this config apply to the size class indicated here. The config apply to all allocations for this _size_ including allocations for non-slice types.

func ConfForString

func ConfForString(s *Store, length int) pointerstore.AllocConfig

Returns the allocation config for the allocations size of a string of length.

It is important to note that this config apply to the size class indicated here. The config apply to all allocations for this _size_ including allocations for non-string types.

func ConfForType

func ConfForType[T any](s *Store) pointerstore.AllocConfig

Returns the allocation config for the allocation size of type T

It is important to note that this config apply to the size class indicated by T. The config apply to all allocations for this _size_ including allocations for types other than T.

func FreeObject

func FreeObject[T any](s *Store, r RefObject[T])

Frees the allocation referenced by r. After this call returns r must never be used again. Any use of the object referenced by r will have unpredicatable behaviour.

Example

You can free memory used by an an allocated object by calling FreeObject(...). The RefObject can no longer be used, and the use of the actual object pointed to will have unpredicatable results.

package main

import (
	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	var store *offheap.Store = offheap.New()

	var ref offheap.RefObject[int] = offheap.AllocObject[int](store)

	offheap.FreeObject(store, ref)
	// You must never use ref again
}
Output:

Example (UseAfterFreePanics)

You can free memory used by an an allocated object by calling FreeObject(...). The RefObject can no longer be used, and the use of the actual object pointed to will have unpredicatable results.

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	var store *offheap.Store = offheap.New()

	var ref offheap.RefObject[int] = offheap.AllocObject[int](store)

	offheap.FreeObject(store, ref)
	// You must never use ref again

	defer func() {
		if err := recover(); err != nil {
			fmt.Println("Use after free panics")
		}
	}()

	ref.Value()
}
Output:

Use after free panics

func FreeSlice

func FreeSlice[T any](s *Store, r RefSlice[T])

Frees the allocation referenced by r. After this call returns r must never be used again. Any use of the slice referenced by r will have unpredicatable behaviour.

func FreeString

func FreeString(s *Store, r RefString)

Frees the allocation referenced by r. After this call returns r must never be used again. Any use of the string referenced by r will have unpredicatable behaviour.

func StatsForSlice

func StatsForSlice[T any](s *Store, capacity int) pointerstore.Stats

Returns the stats for the allocation size of a []T with capacity.

It is important to note that these statistics apply to the size class indicated here. The statistics allocations will capture all allocations for this _size_ including allocations for non-slice types.

func StatsForString

func StatsForString(s *Store, length int) pointerstore.Stats

Returns the stats for the allocations size of a string of length.

It is important to note that these statistics apply to the size class indicated here. The statistics allocations will capture all allocations for this _size_ including allocations for non-slice types.

func StatsForType

func StatsForType[T any](s *Store) pointerstore.Stats

Returns the stats for the allocation size of type T.

It is important to note that these statistics apply to the size class indicated by T. The statistics allocations will capture all allocations for this _size_ including allocations for types other than T.

Types

type RefObject

type RefObject[T any] struct {
	// contains filtered or unexported fields
}

A reference to a typed object. This reference allows us to gain access to an allocated object directly.

It is acceptable, and enouraged, to use RefObject in fields of types which will be managed by a Store. This is acceptable because RefObject does not contain any conventional Go pointers.

func AllocObject

func AllocObject[T any](s *Store) RefObject[T]

Allocates an object of type T. The type T must not contain any pointers in any part of its type. If the type T is found to contain pointers this function will panic.

The values of fields in the newly allocated object will be arbitrary. Unlike Go allocations objects acquired via AllocObject do _not_ have their contents zeroed out.

Example

Calling AllocObject allocates an object and returns a RefObject which acts like a conventional pointer through which you can retrieve the allocated object via RefObject.Value()

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	var store *offheap.Store = offheap.New()

	var ref offheap.RefObject[int] = offheap.AllocObject[int](store)
	var i1 *int = ref.Value()

	var i2 *int = ref.Value()

	if i1 == i2 {
		fmt.Println("This is correct, i1 and i2 are pointers to the same int location")
	}
}
Output:

This is correct, i1 and i2 are pointers to the same int location
Example (BadTypeMap)

You cannot allocate a map type. Maps contain pointers interally and are not allowed.

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	type BadStruct struct {
		//lint:ignore U1000 this field looks unused but is observed by reflection
		mapsHavePointers map[int]int
	}

	defer func() {
		if err := recover(); err != nil {
			fmt.Println("Can't allocate maps")
		}
	}()

	var store *offheap.Store = offheap.New()
	offheap.AllocObject[BadStruct](store)
}
Output:

Can't allocate maps
Example (BadTypePointer)

You cannot allocate a pointer type (obviously).

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	type BadStruct struct {
		//lint:ignore U1000 this field looks unused but is observed by reflection
		pointersHavePointers *int
	}

	defer func() {
		if err := recover(); err != nil {
			fmt.Println("Can't allocate pointers")
		}
	}()

	var store *offheap.Store = offheap.New()
	offheap.AllocObject[BadStruct](store)
}
Output:

Can't allocate pointers
Example (BadTypeSlice)

You cannot allocate a slice type. Slices contain pointers interally and are not allowed.

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	type BadStruct struct {
		//lint:ignore U1000 this field looks unused but is observed by reflection
		slicesHavePointers []int
	}

	defer func() {
		if err := recover(); err != nil {
			fmt.Println("Can't allocate slices (as an object)")
		}
	}()

	var store *offheap.Store = offheap.New()
	offheap.AllocObject[BadStruct](store)
}
Output:

Can't allocate slices (as an object)
Example (BadTypeString)

You cannot allocate a string type. Strings contain pointers interally and are not allowed.

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	type BadStruct struct {
		//lint:ignore U1000 this field looks unused but is observed by reflection
		stringsHavePointers string
	}

	defer func() {
		if err := recover(); err != nil {
			fmt.Println("Can't allocate strings")
		}
	}()

	var store *offheap.Store = offheap.New()
	offheap.AllocObject[BadStruct](store)
}
Output:

Can't allocate strings
Example (ComplexType)

You can allocate objects of complex types, including types with fields which are also of type RefObject. This allows us to build large datastructures, like trees in this example.

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	type Node struct {
		left  offheap.RefObject[Node]
		right offheap.RefObject[Node]
	}

	var store *offheap.Store = offheap.New()

	var refParent offheap.RefObject[Node] = offheap.AllocObject[Node]((store))

	var parent *Node = refParent.Value()

	var refLeft offheap.RefObject[Node] = offheap.AllocObject[Node]((store))

	parent.left = refLeft

	var refRight offheap.RefObject[Node] = offheap.AllocObject[Node]((store))

	parent.right = refRight

	// Re-get the parent pointer
	var reGetParent *Node = refParent.Value()

	if reGetParent.left == refLeft && reGetParent.right == refRight {
		fmt.Println("The mutations of the parent Node are visible via the reference")
	}
}
Output:

The mutations of the parent Node are visible via the reference

func (*RefObject[T]) IsNil

func (r *RefObject[T]) IsNil() bool

Returns true if this RefObject does not point to an allocated object, false otherwise.

func (*RefObject[T]) Value

func (r *RefObject[T]) Value() *T

Returns a pointer to the raw object pointed to by this RefOject.

Care must be taken not to use this object after FreeObject(...) has been called on this RefObject.

type RefSlice

type RefSlice[T any] struct {
	// contains filtered or unexported fields
}

A reference to a slice. This reference allows us to gain access to an allocated slice directly.

It is acceptable, and enouraged, to use RefSlice in fields of types which will be managed by a Store. This is acceptable because RefSlice does not contain any conventional Go pointers, unlike native slices.

func AllocSlice

func AllocSlice[T any](s *Store, length, requestedCapacity int) RefSlice[T]

Allocates a new slice with the desired length and capacity. The capacity of the actual slice may not be the same as requestedCapacity, but it will never be smaller than requestedCapacity.

The contents of the slice will be arbitrary. Unlike Go slices acquired via AllocSlice do _not_ have their contents zeroed out.

Example

Calling AllocSlice allocates a slice and returns a RefSlice which acts like a conventional pointer through which you can retrieve the allocated slice via RefSlice.Value()

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	var store *offheap.Store = offheap.New()

	var ref offheap.RefSlice[int] = offheap.AllocSlice[int](store, 1, 2)

	// Set the first element in the allocated slice
	var s1 []int = ref.Value()
	s1[0] = 1

	// Show that the first element write is visible to other reads
	var s2 []int = ref.Value()

	fmt.Printf("Slice of %v with length %d and capacity %d", s2, len(s2), cap(s1))
}
Output:

Slice of [1] with length 1 and capacity 2

func Append

func Append[T any](s *Store, into RefSlice[T], value T) RefSlice[T]

Returns a new RefSlice pointing to a slice whose size and contents is the same as append(into.Value(), value).

After this function returns into is no longer a valid RefSlice, and will behave as if Free(...) was called on it. Internally there is an optimisation which _may_ reuse the existing allocation slot if possible. But externally this function behaves as if a new allocation is made and the old one freed.

Example

You can append an element to an allocated RefSlice

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	var store *offheap.Store = offheap.New()

	var ref1 offheap.RefSlice[int] = offheap.AllocSlice[int](store, 1, 2)

	// Set the first element in the allocated slice
	var s1 []int = ref1.Value()
	s1[0] = 1

	// Append a second element to the slice
	var ref2 offheap.RefSlice[int] = offheap.Append(store, ref1, 2)

	var s2 []int = ref2.Value()

	fmt.Printf("Slice of %v with length %d and capacity %d", s2, len(s2), cap(s1))
}
Output:

Slice of [1 2] with length 2 and capacity 2
Example (OldRef)

After call to append the old RefSlice is no longer valid for use

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	var store *offheap.Store = offheap.New()

	var ref1 offheap.RefSlice[int] = offheap.AllocSlice[int](store, 1, 2)

	// Set the first element in the allocated slice
	var s1 []int = ref1.Value()
	s1[0] = 1

	// Append a second element to the slice
	offheap.Append(store, ref1, 2)

	defer func() {
		if err := recover(); err != nil {
			fmt.Printf("The RefSlice passed into Append cannot be used after")
		}
	}()

	ref1.Value()
}
Output:

The RefSlice passed into Append cannot be used after

func AppendSlice

func AppendSlice[T any](s *Store, into RefSlice[T], fromSlice []T) RefSlice[T]

Returns a new RefSlice pointing to a slice whose size and contents is the same as append(into.Value(), fromSlice...).

After this function returns into is no longer a valid RefSlice, and will behave as if Free(...) was called on it. Internally there is an optimisation which _may_ reuse the existing allocation slot if possible. But externally this function behaves as if a new allocation is made and the old one freed.

Example

You can append a slice to a RefSlice. This will create a new RefSlice with the original slice and the slice passed in appended together.

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	var store *offheap.Store = offheap.New()

	var ref1 offheap.RefSlice[int] = offheap.AllocSlice[int](store, 1, 2)

	// Set the first element in the allocated slice
	var s1 []int = ref1.Value()
	s1[0] = 1

	// Append a second element to the slice
	var ref2 offheap.RefSlice[int] = offheap.AppendSlice(store, ref1, []int{2, 3})

	var s2 []int = ref2.Value()

	fmt.Printf("Slice of %v with length %d and capacity %d", s2, len(s2), cap(s2))
}
Output:

Slice of [1 2 3] with length 3 and capacity 4
Example (OldRef)

After call to AppendSlice the old RefSlice is no longer valid for use.

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	var store *offheap.Store = offheap.New()

	var ref1 offheap.RefSlice[int] = offheap.AllocSlice[int](store, 1, 2)

	// Set the first element in the allocated slice
	var s1 []int = ref1.Value()
	s1[0] = 1

	// Append a second element to the slice
	offheap.AppendSlice(store, ref1, []int{2, 3})

	defer func() {
		if err := recover(); err != nil {
			fmt.Printf("The RefSlice passed into AppendSlice cannot be used after")
		}
	}()

	ref1.Value()
}
Output:

The RefSlice passed into AppendSlice cannot be used after

func ConcatSlices

func ConcatSlices[T any](s *Store, slices ...[]T) RefSlice[T]

Allocates a new slice which contains the elements of slices concatenated together

Example

You can allocate a RefSlice by passing in a number of slices to be concatenated together

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	var store *offheap.Store = offheap.New()

	slice1 := []int{1, 2}
	slice2 := []int{3, 4}
	slice3 := []int{5, 6}

	var ref offheap.RefSlice[int] = offheap.ConcatSlices[int](store, slice1, slice2, slice3)

	var s1 []int = ref.Value()

	fmt.Printf("Slice of %v with length %d and capacity %d", s1, len(s1), cap(s1))
}
Output:

Slice of [1 2 3 4 5 6] with length 6 and capacity 8

func (*RefSlice[T]) IsNil

func (r *RefSlice[T]) IsNil() bool

Returns true if this RefSlice does not point to an allocated slice, false otherwise.

func (*RefSlice[T]) Value

func (r *RefSlice[T]) Value() []T

Returns the raw slice pointed to by this RefSlice.

Care must be taken not to use this slice after FreeSlice(...) has been called on this RefSlice.

type RefString

type RefString struct {
	// contains filtered or unexported fields
}

A reference to a string. This reference allows us to gain access to an allocated string directly.

It is acceptable, and enouraged, to use RefString in fields of types which will be managed by a Store. This is acceptable because RefString does not contain any conventional Go pointers, unlike native strings.

func AllocStringFromBytes

func AllocStringFromBytes(s *Store, bytes []byte) RefString

Allocates a new string whose size and contents will be the same as found in bytes.

Example

Calling AllocStringFromBytes allocates a string and returns a RefString which acts like a conventional pointer through which you can retrieve the allocated string via RefString.Value()

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	var store *offheap.Store = offheap.New()

	var ref offheap.RefString = offheap.AllocStringFromBytes(store, []byte("allocated"))

	// Set the first element in the allocated slice
	var s1 string = ref.Value()

	fmt.Printf("String of %q", s1)
}
Output:

String of "allocated"

func AllocStringFromString

func AllocStringFromString(s *Store, str string) RefString

Allocates a new string whose size and contents will be the same as found in str.

Example

Calling AllocStringFromString allocates a string and returns a RefString which acts like a conventional pointer through which you can retrieve the allocated string via RefString.Value()

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	var store *offheap.Store = offheap.New()

	var ref offheap.RefString = offheap.AllocStringFromString(store, "allocated")

	// Set the first element in the allocated slice
	var s1 string = ref.Value()

	fmt.Printf("String of %q", s1)
}
Output:

String of "allocated"

func AppendString

func AppendString(s *Store, into RefString, value string) RefString

Returns a new RefString pointing to a string whose size and contents is the same as into.Value() + value.

After this function returns into is no longer a valid RefString, and will behave as if Free(...) was called on it. Internally there is an optimisation which _may_ reuse the existing allocation slot if possible. But externally this function behaves as if a new allocation is made and the old one freed.

Example

You can append a string to an allocated RefString

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	var store *offheap.Store = offheap.New()

	var ref1 offheap.RefString = offheap.AllocStringFromString(store, "allocated")

	// AppendString a second element to the string
	var ref2 offheap.RefString = offheap.AppendString(store, ref1, " and appended")

	var s2 string = ref2.Value()

	fmt.Printf("String of %q", s2)
}
Output:

String of "allocated and appended"
Example (OldRef)

After call to append the old RefString is no longer valid for use

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	var store *offheap.Store = offheap.New()

	var ref1 offheap.RefString = offheap.AllocStringFromString(store, "allocated")

	// AppendString a second element to the string
	offheap.AppendString(store, ref1, " and appended")

	defer func() {
		if err := recover(); err != nil {
			fmt.Printf("The RefString passed into AppendString cannot be used after")
		}
	}()

	ref1.Value()
}
Output:

The RefString passed into AppendString cannot be used after

func ConcatStrings

func ConcatStrings(s *Store, strs ...string) RefString

Allocates a new string which contains the elements of strs concatenated together.

Example

You can allocate a RefString by passing in a number of strings to be concatenated together

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	var store *offheap.Store = offheap.New()

	var ref offheap.RefString = offheap.ConcatStrings(store, "all", "oca", "ted")

	var s1 string = ref.Value()

	fmt.Printf("String of %q", s1)
}
Output:

String of "allocated"

func (*RefString) IsNil

func (r *RefString) IsNil() bool

Returns true if this RefString does not point to an allocated string, false otherwise.

func (*RefString) Value

func (r *RefString) Value() string

Returns the raw string pointed to by this RefString.

Care must be taken not to use this string after FreeString(...) has been called on this RefString.

type Reference

type Reference[T any] interface {
	RefString | RefSlice[T] | RefObject[T]
}

This type constraint allows us to build generic types which accept any Reference type. There is an awkward problem if your type is RefString, because you are forced to include an _unused_ parameterised type. We may learn to live with this peacefully in time.

Example (RefObject)

Here we store and retrieve a RefObject[int] using a Reference typed container.

This example exists simply to illustrate how the Reference type can be used. I don't think it's obvious without at least one example.

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	// The ReferenceMap type is capable of storing either RefString,
	// RefSlice or RefObject types
	type ReferenceMap[T any, R offheap.Reference[T]] struct {
		rMap map[int]R
	}

	// Here we instantiate a ReferenceMap which can store RefObject references
	var refObjectMap ReferenceMap[int, offheap.RefObject[int]] = ReferenceMap[int, offheap.RefObject[int]]{
		rMap: make(map[int]offheap.RefObject[int]),
	}

	// Create a RefObject
	var store *offheap.Store = offheap.New()
	var ref offheap.RefObject[int] = offheap.AllocObject[int](store)
	var intValue *int = ref.Value()
	*intValue = 127

	// Store and retrieve that RefObject
	refObjectMap.rMap[1] = ref
	var refOut offheap.RefObject[int] = refObjectMap.rMap[1]

	fmt.Printf("Stored and retrieved %v", *(refOut.Value()))
}
Output:

Stored and retrieved 127
Example (RefSlice)

Here we store and retrieve a RefSlice[byte] using a Reference typed container.

This example exists simply to illustrate how the Reference type can be used. I don't think it's obvious without at least one example.

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	// The ReferenceMap type is capable of storing either RefString,
	// RefSlice or RefObject types
	type ReferenceMap[T any, R offheap.Reference[T]] struct {
		rMap map[int]R
	}

	// Here we instantiate a ReferenceMap which can store RefSlice references
	var refSliceMap ReferenceMap[byte, offheap.RefSlice[byte]] = ReferenceMap[byte, offheap.RefSlice[byte]]{
		rMap: make(map[int]offheap.RefSlice[byte]),
	}

	// Create a RefSlice
	var store *offheap.Store = offheap.New()
	var ref offheap.RefSlice[byte] = offheap.ConcatSlices[byte](store, []byte("ref slice reference"))

	// Store and retrieve that RefSlice
	refSliceMap.rMap[1] = ref
	var refOut offheap.RefSlice[byte] = refSliceMap.rMap[1]

	fmt.Printf("Stored and retrieved %q", refOut.Value())
}
Output:

Stored and retrieved "ref slice reference"
Example (RefString)

Here we store and retrieve a RefString using a Reference typed container. We should note that instantiating a Reference type requires us to define a type for T. This type is unused because RefString, unlike RefObject and RefSlice, is not a parameterised type. This is awkward and inelegant, but survivable.

This example exists simply to illustrate how the Reference type can be used. I don't think it's obvious without at least one example.

package main

import (
	"fmt"

	"github.com/fmstephe/memorymanager/offheap"
)

func main() {
	// The ReferenceMap type is capable of storing either RefString,
	// RefSlice or RefObject types
	type ReferenceMap[T any, R offheap.Reference[T]] struct {
		rMap map[int]R
	}

	// Here we instantiate a ReferenceMap which can store RefString references
	var refStringMap ReferenceMap[byte, offheap.RefString] = ReferenceMap[byte, offheap.RefString]{
		rMap: make(map[int]offheap.RefString),
	}

	// Create a RefString
	var store *offheap.Store = offheap.New()
	var ref offheap.RefString = offheap.ConcatStrings(store, "ref string reference")

	// Store and retrieve that RefString
	refStringMap.rMap[1] = ref
	var refOut offheap.RefString = refStringMap.rMap[1]

	fmt.Printf("Stored and retrieved %q", refOut.Value())
}
Output:

Stored and retrieved "ref string reference"

type Store

type Store struct {
	// contains filtered or unexported fields
}

func New

func New() *Store

Returns a new *Store.

This store manages allocation and freeing of any offheap allocated objects.

func NewSized

func NewSized(slabSize int) *Store

Returns a new *Store.

The size of each slab, contiguous chunk of memory where allocations are organised, is set to be at least slabSize. If slabSize is not a power of two, then slabSize will be rounded up to the nearest power of two and then used.

Some users may have real need for a Store with a non-standard slab-size. But the motivating use of this function was to allow the creation of Stores with small slab sizes to allow faster tests with reduced memory usage. Most users will probably prefer to use the default New() above.

func (*Store) AllocConfigs

func (s *Store) AllocConfigs() []pointerstore.AllocConfig

Returns the allocation config across all allocation size classes for this Store.

There are helper methods which allow the user to easily get the config for a single size class for object, slices and string allocations.

func (*Store) Destroy

func (s *Store) Destroy() error

Releases the memory allocated by the Store back to the operating system. After this method is called the Store is completely unusable.

There may be some use-cases for this in real systems. But the motivating use case for this method was allowing us to release memory of Stores created in unit tests (we create a lot of them). Without this method the tests, especially the fuzz tests, would OOM very quickly. Right now I would expect that most (all?) Stores will live for the entire lifecycle of the program they are used in, so this method probably won't be used in most cases.

func (*Store) Stats

func (s *Store) Stats() []pointerstore.Stats

Returns the statistics across all allocation size classes for this Store.

There are helper methods which allow the user to easily get the statistics for a single size class for object, slices and string allocations.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL