Expand description
Description
Onsen provides a hot Pool for objects. In most cases allocation from this Pool is faster and offers better locality than the standard allocator.
Details
The first block in a Pool is size [Entry<T>; E]
, when a Pool runs out of storage it
allocates a new block from the system which is twice as big as the previous block. E should
be be optimized for the intended use. That is blocks should become close or equal to multiples
of cache lines, pages, huge pages, whatever makes most sense. There is a OptimalBlockSize
trait which does this calculations. Memory allocation happens only when necessary, creating a
pool is a cheap operation.
Box and Rc
Onsen comes with its own Box and Rc/Weak implementations that wrap the underlying Pool in a safe way.
Slots and safety
Allocating from a Pool returns Slot handles. These are lightweight abstractions to memory addresses, they do not keep a relation to the Pool they are allocated from. The rationale for this design is to make them usable in a VM that uses NaN tagging.
Because of this Slots need to be handled with care and certain contracts need to be enforced. The library provides some help to ensure correctness. The more expensive checks are only run in debug mode. Few things can not be asserted and are guarded by unsafe functions.
- Slots must be given back to the Pool they originate from.
- This is asserted only in debug mode because it is more expensive.
- Slots must not outlive the Pool they are allocated from.
- When a Pool gets dropped while it still has live allocations it will panic in debug mode.
- When a Pool with live allocations gets dropped in release mode it leaks its memory. This is unfortunate but ensures memory safety of the program.
- There is
pool.leak()
which drops a pool while leaking its memory blocks. This can be used when one will never try to free memory obtained from that Pool.
- Slots must be freed only once.
- This is always asserted. But the assertion may fail when the slot got allocated again.
- References obtained from Slots must not outlive the freeing of the Slot.
- This is the main reason that makes the Slot freeing functions unsafe. There is no way for a Pool to know if references are still in use. One should provide a safe abstraction around referenced to enforce this.
- Slots can hold uninitialized data, then no references or Pins must be taken from them.
- This is always asserted.
- Obtaining a
&mut T
from a Slot is mutually exclusive to obtaining aPin<&mut T>
.- This is always asserted.
- Any mutable reference obtained while initializing an uninitialized Slot must be dropped
before calling
slot.assume_init()
. This would break the Pin guarantees.- This is part of the reason that
slot.get_uninit()
andslot.assume_init()
are unsafe and must be enforced by the programmer.
- This is part of the reason that
- All the above applies to the NaN tagging facilities
slot.into_u64()
,Slot::from_u64()
andSlot::from_u64_masked()
. - The NaN tagging facilities allow to duplicate slots which is not supported. Be careful when convert an u64 back to a Slot.
Benchmarking
Onsen uses criterion for benchmarking, since onsen is made for singlethreaded application its best to be tested when locked on a single CPU core (nowadays CPU cores have different performance characteristics).
taskset 2 cargo bench
Will produce target/criterion/report/index.html
.
Macros
Convenient helper that calls Pool::new()
with a optimized size for E.
Structs
A Box for Pool allocated objects. This wraps Slots in a safe way. Dropping a Box will ensure that the destructor is called and the memory is given back to the pool.
A Memory Pool holding objects of type T with a initial block size of E objects.
A reference counted smart pointer for Pool allocated objects. This wraps Slots in a safe
way. Rc’s need a Pool holding RcInner<T>
, not T
.
Handle to allocated memory. This wraps an internal pointer to the allocation and provides
an API for accessing the content. To free memory slots must eventually be given back to
the pool they belong to by pool.free()
, pool.forget()
or pool.take()
. Slots do not
track which Pool they belong to. It is the responsibility of the user to give them back to
the correct pool and ensure that they do not outlive the pool they belong to. In debug
mode it asserted that a slot belongs to the pool when it is given back. Safe abstractions
should track the slots pool.
Weak references do not keep the object alive.
Traits
Helper trait for configuring the optimal block size E. Note that Objects must be smaller than the granularity picked here. Ideally much smaller (by a factor greater than 10).