zephyr/lib/os/heap.h
Andy Ross aa4227754c lib/os: Add sys_heap, a new/simpler/faster memory allocator
The existing mem_pool implementation has been an endless source of
frustration.  It's had alignment bugs, it's had racy behavior.  It's
never been particularly fast.  It's outrageously complicated to
configure statically.  And while its fragmentation resistance and
overhead on small blocks is good, it's space efficiencey has always
been very poor due to the four-way buddy scheme.

This patch introduces sys_heap.  It's a more or less conventional
segregated fit allocator with power-of-two buckets.  It doesn't expose
its level structure to the user at all, simply taking an arbitrarily
aligned pointer to memory.  It stores all metadata inside the heap
region.  It allocates and frees by simple pointer and not block ID.
Static initialization is trivial, and runtime initialization is only a
few cycles to format and add one block to a list header.

It has excellent space efficiency.  Chunks can be split arbitrarily in
8 byte units.  Overhead is only four bytes per allocated chunk (eight
bytes for heaps >256kb or on 64 bit systems), plus a log2-sized array
of 2-word bucket headers.  No coarse alignment restrictions on blocks,
they can be split and merged (in units of 8 bytes) arbitrarily.

It has good fragmentation resistance.  Freed blocks are always
immediately merged with adjacent free blocks.  Allocations are
attempted from a sample of the smallest bucket that might fit, falling
back rapidly to the smallest block guaranteed to fit.  Split memory
remaining in the chunk is always returned immediately to the heap for
other allocation.

It has excellent performance with firmly bounded runtime.  All
operations are constant time (though there is a search of the smallest
bucket that has a compile-time-configurable upper bound, setting this
to extreme values results in an effectively linear search of the
list), objectively fast (about a hundred instructions) and amenable to
locked operation.  No more need for fragile lock relaxation trickery.

It also contains an extensive validation and stress test framework,
something that was sorely lacking in the previous implementation.

Note that sys_heap is not a compatible API with sys_mem_pool and
k_mem_pool.  Partial wrappers for those (now-) legacy APIs will appear
later and a deprecation strategy needs to be chosen.

Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
2020-04-14 10:05:55 -07:00

165 lines
4.1 KiB
C

/*
* Copyright (c) 2019 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_LIB_OS_HEAP_H_
#define ZEPHYR_INCLUDE_LIB_OS_HEAP_H_
/*
* Internal heap APIs
*/
/* Theese validation checks are non-trivially expensive, so enable
* only when debugging the heap code. They shouldn't be routine
* assertions.
*/
#ifdef CONFIG_SYS_HEAP_VALIDATE
#define CHECK(x) __ASSERT(x, "")
#else
#define CHECK(x) /**/
#endif
/* Chunks are identified by their offset in 8 byte units from the
* first address in the buffer (a zero-valued chunkid_t is used as a
* null; that chunk would always point into the metadata at the start
* of the heap and cannot be allocated). They are prefixed by a
* variable size header that depends on the size of the heap. Heaps
* with fewer than 2^15 units (256kb) of storage use shorts to store
* the fields, otherwise the units are 32 bit integers for a 16Gb heap
* space (larger spaces really aren't in scope for this code, but
* could be handled similarly I suppose). Because of that design
* there's a certain amount of boilerplate API needed to expose the
* field accessors since we can't use natural syntax.
*
* The fields are:
* SIZE_AND_USED: the total size (including header) of the chunk in
* 8-byte units. The top bit stores a "used" flag.
* LEFT_SIZE: The size of the left (next lower chunk in memory)
* neighbor chunk.
* FREE_PREV: Chunk ID of the previous node in a free list.
* FREE_NEXT: Chunk ID of the next node in a free list.
*
* The free lists are circular lists, one for each power-of-two size
* category. The free list pointers exist only for free chunks,
* obviously. This memory is part of the user's buffer when
* allocated.
*/
typedef size_t chunkid_t;
#define CHUNK_UNIT 8
enum chunk_fields { SIZE_AND_USED, LEFT_SIZE, FREE_PREV, FREE_NEXT };
struct z_heap {
u64_t *buf;
struct z_heap_bucket *buckets;
u32_t len;
u32_t size_mask;
u32_t chunk0;
u32_t avail_buckets;
};
struct z_heap_bucket {
chunkid_t next;
size_t list_size;
};
static inline bool big_heap(struct z_heap *h)
{
return sizeof(size_t) > 4 || h->len > 0x7fff;
}
static inline size_t chunk_field(struct z_heap *h, chunkid_t c,
enum chunk_fields f)
{
void *cmem = &h->buf[c];
if (big_heap(h)) {
return ((u32_t *)cmem)[f];
} else {
return ((u16_t *)cmem)[f];
}
}
static inline void chunk_set(struct z_heap *h, chunkid_t c,
enum chunk_fields f, chunkid_t val)
{
CHECK(c >= h->chunk0 && c < h->len);
CHECK((val & ~((h->size_mask << 1) + 1)) == 0);
CHECK((val & h->size_mask) < h->len);
void *cmem = &h->buf[c];
if (big_heap(h)) {
((u32_t *)cmem)[f] = (u32_t) val;
} else {
((u16_t *)cmem)[f] = (u16_t) val;
}
}
static inline chunkid_t used(struct z_heap *h, chunkid_t c)
{
return (chunk_field(h, c, SIZE_AND_USED) & ~h->size_mask) != 0;
}
static ALWAYS_INLINE chunkid_t size(struct z_heap *h, chunkid_t c)
{
return chunk_field(h, c, SIZE_AND_USED) & h->size_mask;
}
static inline void chunk_set_used(struct z_heap *h, chunkid_t c,
bool used)
{
chunk_set(h, c, SIZE_AND_USED,
size(h, c) | (used ? (h->size_mask + 1) : 0));
}
static inline chunkid_t left_size(struct z_heap *h, chunkid_t c)
{
return chunk_field(h, c, LEFT_SIZE);
}
static inline chunkid_t free_prev(struct z_heap *h, chunkid_t c)
{
return chunk_field(h, c, FREE_PREV);
}
static inline chunkid_t free_next(struct z_heap *h, chunkid_t c)
{
return chunk_field(h, c, FREE_NEXT);
}
static inline chunkid_t left_chunk(struct z_heap *h, chunkid_t c)
{
return c - left_size(h, c);
}
static inline chunkid_t right_chunk(struct z_heap *h, chunkid_t c)
{
return c + size(h, c);
}
static inline size_t chunk_header_bytes(struct z_heap *h)
{
return big_heap(h) ? 8 : 4;
}
static inline size_t chunksz(size_t bytes)
{
return (bytes + CHUNK_UNIT - 1) / CHUNK_UNIT;
}
static inline size_t bytes_to_chunksz(struct z_heap *h, size_t bytes)
{
return chunksz(chunk_header_bytes(h) + bytes);
}
static int bucket_idx(struct z_heap *h, size_t sz)
{
/* A chunk of size 2 is the minimum size on big heaps */
return 31 - __builtin_clz(sz) - (big_heap(h) ? 1 : 0);
}
#endif /* ZEPHYR_INCLUDE_LIB_OS_HEAP_H_ */