mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-06 07:38:48 +00:00
[mem]: Document the package
This commit is contained in:
+88
-23
@@ -1,34 +1,99 @@
|
|||||||
/*
|
/*
|
||||||
package mem implements various types of allocators.
|
The `mem` package implements various allocators and provides utility functions
|
||||||
|
for dealing with memory, pointers and slices.
|
||||||
|
|
||||||
|
The documentation below describes basic concepts, applicable to the `mem`
|
||||||
|
package.
|
||||||
|
|
||||||
An example of how to use the `Tracking_Allocator` to track subsequent allocations
|
## Pointers, multipointers, and slices
|
||||||
in your program and report leaks and bad frees:
|
|
||||||
|
|
||||||
Example:
|
A *pointer* is an abstraction of an *address*, a numberic value representing the
|
||||||
package foo
|
location of an object in memory. That object is said to be *pointed to* by the
|
||||||
|
pointer. To obtain the address of a pointer, cast it to `uintptr`.
|
||||||
|
|
||||||
import "core:mem"
|
A multipointer is a pointer that points to multiple objects. Unlike a pointer,
|
||||||
import "core:fmt"
|
a multipointer can be indexed, but does not have a definite length. A slice is
|
||||||
|
a pointer that points to multiple objects equipped with the length, specifying
|
||||||
|
the amount of objects a slice points to.
|
||||||
|
|
||||||
_main :: proc() {
|
When object's values are read through a pointer, that operation is called a
|
||||||
// do stuff
|
*load* operation. When memory is read through a pointer, that operation is
|
||||||
}
|
called a *store* operation. Both of these operations can be called a *memory
|
||||||
|
access operation*.
|
||||||
|
|
||||||
main :: proc() {
|
## Allocators
|
||||||
track: mem.Tracking_Allocator
|
|
||||||
mem.tracking_allocator_init(&track, context.allocator)
|
|
||||||
defer mem.tracking_allocator_destroy(&track)
|
|
||||||
context.allocator = mem.tracking_allocator(&track)
|
|
||||||
|
|
||||||
_main()
|
In C and C++ memory models, allocations of objects in memory are typically
|
||||||
|
treated individually with a generic allocator (The `malloc` function). Which in
|
||||||
|
some scenarios can lead to poor cache utilization, slowdowns on individual
|
||||||
|
objects' memory management and growing complexity of the code needing to keep
|
||||||
|
track of the pointers and their lifetimes.
|
||||||
|
|
||||||
for _, leak in track.allocation_map {
|
Using different kinds of *allocators* for different purposes can solve these
|
||||||
fmt.printf("%v leaked %m\n", leak.location, leak.size)
|
problems. The allocators are typically optimized for specific use-cases and
|
||||||
}
|
can potentially simplify the memory management code.
|
||||||
for bad_free in track.bad_free_array {
|
|
||||||
fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)
|
For example, in the context of making a game, having an Arena allocator could
|
||||||
}
|
simplify allocations of any temporary memory, because the programmer doesn't
|
||||||
}
|
have to keep track of which objects need to be freed every time they are
|
||||||
|
allocated, because at the end of every frame the whole allocator is reset to
|
||||||
|
its initial state and all objects are freed at once.
|
||||||
|
|
||||||
|
The allocators have different kinds of restrictions on object lifetimes, sizes,
|
||||||
|
alignment and can be a significant gain, if used properly. Odin supports
|
||||||
|
allocators on a language level.
|
||||||
|
|
||||||
|
Operations such as `new`, `free` and `delete` by default will use
|
||||||
|
`context.allocator`, which can be overridden by the user. When an override
|
||||||
|
happens all called functions will inherit the new context and use the same
|
||||||
|
allocator.
|
||||||
|
|
||||||
|
## Alignment
|
||||||
|
|
||||||
|
An address is said to be *aligned to `N` bytes*, if the addresses's numeric
|
||||||
|
value is divisible by `N`. The number `N` in this case can be referred to as
|
||||||
|
the *alignment boundary*. Typically an alignment is a power of two integer
|
||||||
|
value.
|
||||||
|
|
||||||
|
A *natural alignment* of an object is typically equal to its size. For example
|
||||||
|
a 16 bit integer has a natural alignment of 2 bytes. When an object is not
|
||||||
|
located on its natural alignment boundary, accesses to that object are
|
||||||
|
considered *unaligned*.
|
||||||
|
|
||||||
|
Some machines issue a hardware **exception**, or experience **slowdowns** when a
|
||||||
|
memory access operation occurs from an unaligned address. Examples of such
|
||||||
|
operations are:
|
||||||
|
|
||||||
|
- SIMD instructions on x86. These instructions require all memory accesses to be
|
||||||
|
on an address that is aligned to 16 bytes.
|
||||||
|
- On ARM unaligned loads have an extra cycle penalty.
|
||||||
|
|
||||||
|
As such, many operations that allocate memory in this package allow to
|
||||||
|
explicitly specify the alignment of allocated pointers/slices. The default
|
||||||
|
alignment for all operations is specified in a constant `mem.DEFAULT_ALIGNMENT`.
|
||||||
|
|
||||||
|
## Zero by default
|
||||||
|
|
||||||
|
Whenever new memory is allocated, via an allocator, or on the stack, by default
|
||||||
|
Odin will zero-initialize that memory, even if it wasn't explicitly
|
||||||
|
initialized. This allows for some convenience in certain scenarios and ease of
|
||||||
|
debugging, which will not be described in detail here.
|
||||||
|
|
||||||
|
However zero-initialization can be a cause of slowdowns, when allocating large
|
||||||
|
buffers. For this reason, allocators have `*_non_zeroed` modes of allocation
|
||||||
|
that allow the user to request for uninitialized memory and will avoid a
|
||||||
|
relatively expensive zero-filling of the buffer.
|
||||||
|
|
||||||
|
## Naming conventions
|
||||||
|
|
||||||
|
The word `size` is used to denote the **size in bytes**. The word `length` is
|
||||||
|
used to denote the count of objects.
|
||||||
|
|
||||||
|
Higher-level allocation functions follow the following naming scheme:
|
||||||
|
|
||||||
|
- `new`: Allocates a single object
|
||||||
|
- `free`: Free a single object (opposite of `new`)
|
||||||
|
- `make`: Allocate a group of objects
|
||||||
|
- `delete`: Free a group of objects (opposite of `make`)
|
||||||
*/
|
*/
|
||||||
package mem
|
package mem
|
||||||
|
|||||||
@@ -18,6 +18,37 @@ Tracking_Allocator_Bad_Free_Entry :: struct {
|
|||||||
location: runtime.Source_Code_Location,
|
location: runtime.Source_Code_Location,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
An example of how to use the `Tracking_Allocator` to track subsequent allocations
|
||||||
|
in your program and report leaks and bad frees:
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
package foo
|
||||||
|
|
||||||
|
import "core:mem"
|
||||||
|
import "core:fmt"
|
||||||
|
|
||||||
|
_main :: proc() {
|
||||||
|
// do stuff
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: proc() {
|
||||||
|
track: mem.Tracking_Allocator
|
||||||
|
mem.tracking_allocator_init(&track, context.allocator)
|
||||||
|
defer mem.tracking_allocator_destroy(&track)
|
||||||
|
context.allocator = mem.tracking_allocator(&track)
|
||||||
|
|
||||||
|
_main()
|
||||||
|
|
||||||
|
for _, leak in track.allocation_map {
|
||||||
|
fmt.printf("%v leaked %m\n", leak.location, leak.size)
|
||||||
|
}
|
||||||
|
for bad_free in track.bad_free_array {
|
||||||
|
fmt.printf("%v allocation %p was freed badly\n", bad_free.location, bad_free.memory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
Tracking_Allocator :: struct {
|
Tracking_Allocator :: struct {
|
||||||
backing: Allocator,
|
backing: Allocator,
|
||||||
allocation_map: map[rawptr]Tracking_Allocator_Entry,
|
allocation_map: map[rawptr]Tracking_Allocator_Entry,
|
||||||
|
|||||||
Reference in New Issue
Block a user