You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

535 lines
16 KiB

#ifndef GHEAP_H
#define GHEAP_H
/*
* Generalized heap implementation for C99.
*
* Don't forget passing -DNDEBUG option to the compiler when creating optimized
* builds. This significantly speeds up gheap code by removing debug assertions.
*
* Author: Aliaksandr Valialkin <valyala@gmail.com>.
*/
/*******************************************************************************
* Interface.
******************************************************************************/
#include <stddef.h> /* for size_t */
#include <stdint.h> /* for SIZE_MAX */
/*
* Less comparer must return non-zero value if a < b.
* ctx is the gheap_ctx->less_comparer_ctx.
* Otherwise it must return 0.
*/
typedef int (*gheap_less_comparer_t)(const void *ctx, const void *a,
const void *b);
/*
* Moves the item from src to dst.
*/
typedef void (*gheap_item_mover_t)(void *dst, const void *src);
/*
* Gheap context.
* This context must be passed to every gheap function.
*/
struct gheap_ctx
{
/*
* How much children each heap item can have.
*/
size_t fanout;
/*
* A chunk is a tuple containing fanout items arranged sequentially in memory.
* A page is a subheap containing page_chunks chunks arranged sequentially
* in memory.
* The number of chunks in a page is an arbitrary integer greater than 0.
*/
size_t page_chunks;
/*
* The size of each item in bytes.
*/
size_t item_size;
gheap_less_comparer_t less_comparer;
const void *less_comparer_ctx;
gheap_item_mover_t item_mover;
};
/*
* Returns parent index for the given child index.
* Child index must be greater than 0.
* Returns 0 if the parent is root.
*/
static inline size_t gheap_get_parent_index(const struct gheap_ctx *ctx,
size_t u);
/*
* Returns the index of the first child for the given parent index.
* Parent index must be less than SIZE_MAX.
* Returns SIZE_MAX if the index of the first child for the given parent
* cannot fit size_t.
*/
static inline size_t gheap_get_child_index(const struct gheap_ctx *ctx,
size_t u);
/*
* Returns a pointer to the first non-heap item using less_comparer
* for items' comparison.
* Returns the index of the first non-heap item.
* Returns heap_size if base points to valid max heap with the given size.
*/
static inline size_t gheap_is_heap_until(const struct gheap_ctx *ctx,
const void *base, size_t heap_size);
/*
* Returns non-zero if base points to valid max heap. Returns zero otherwise.
* Uses less_comparer for items' comparison.
*/
static inline int gheap_is_heap(const struct gheap_ctx *ctx,
const void *base, size_t heap_size);
/*
* Makes max heap from items base[0] ... base[heap_size-1].
* Uses less_comparer for items' comparison.
*/
static inline void gheap_make_heap(const struct gheap_ctx *ctx,
void *base, size_t heap_size);
/*
* Pushes the item base[heap_size-1] into max heap base[0] ... base[heap_size-2]
* Uses less_comparer for items' comparison.
*/
static inline void gheap_push_heap(const struct gheap_ctx *ctx,
void *base, size_t heap_size);
/*
* Pops the maximum item from max heap base[0] ... base[heap_size-1] into
* base[heap_size-1].
* Uses less_comparer for items' comparison.
*/
static inline void gheap_pop_heap(const struct gheap_ctx *ctx,
void *base, size_t heap_size);
/*
* Sorts items in place of max heap in ascending order.
* Uses less_comparer for items' comparison.
*/
static inline void gheap_sort_heap(const struct gheap_ctx *ctx,
void *base, size_t heap_size);
/*
* Swaps the item outside the heap with the maximum item inside
* the heap and restores heap invariant.
*/
static inline void gheap_swap_max_item(const struct gheap_ctx *const ctx,
void *const base, const size_t heap_size, void *item);
/*
* Restores max heap invariant after item's value has been increased,
* i.e. less_comparer(old_item, new_item) != 0.
*/
static inline void gheap_restore_heap_after_item_increase(
const struct gheap_ctx *ctx,
void *base, size_t heap_size, size_t modified_item_index);
/*
* Restores max heap invariant after item's value has been decreased,
* i.e. less_comparer(new_item, old_item) != 0.
*/
static inline void gheap_restore_heap_after_item_decrease(
const struct gheap_ctx *ctx,
void *base, size_t heap_size, size_t modified_item_index);
/*
* Removes the given item from the heap and puts it into base[heap_size-1].
* Uses less_comparer for items' comparison.
*/
static inline void gheap_remove_from_heap(const struct gheap_ctx *ctx,
void *base, size_t heap_size, size_t item_index);
/*******************************************************************************
* Implementation.
*
* Define all functions inline, so compiler will be able optimizing out common
* args (fanout, page_chunks, item_size, less_comparer and item_mover),
* which are usually constants, using contant folding optimization
* ( http://en.wikipedia.org/wiki/Constant_folding ).
*****************************************************************************/
#include <assert.h> /* for assert */
#include <stddef.h> /* for size_t */
#include <stdint.h> /* for uintptr_t, SIZE_MAX and UINTPTR_MAX */
static inline size_t gheap_get_parent_index(const struct gheap_ctx *const ctx,
size_t u)
{
assert(u > 0);
const size_t fanout = ctx->fanout;
const size_t page_chunks = ctx->page_chunks;
--u;
if (page_chunks == 1) {
return u / fanout;
}
if (u < fanout) {
/* Parent is root. */
return 0;
}
assert(page_chunks <= SIZE_MAX / fanout);
const size_t page_size = fanout * page_chunks;
size_t v = u % page_size;
if (v >= fanout) {
/* Fast path. Parent is on the same page as the child. */
return u - v + v / fanout;
}
/* Slow path. Parent is on another page. */
v = u / page_size - 1;
const size_t page_leaves = (fanout - 1) * page_chunks + 1;
u = v / page_leaves + 1;
return u * page_size + v % page_leaves - page_leaves + 1;
}
static inline size_t gheap_get_child_index(const struct gheap_ctx *const ctx,
size_t u)
{
assert(u < SIZE_MAX);
const size_t fanout = ctx->fanout;
const size_t page_chunks = ctx->page_chunks;
if (page_chunks == 1) {
if (u > (SIZE_MAX - 1) / fanout) {
/* Child overflow. */
return SIZE_MAX;
}
return u * fanout + 1;
}
if (u == 0) {
/* Root's child is always 1. */
return 1;
}
assert(page_chunks <= SIZE_MAX / fanout);
const size_t page_size = fanout * page_chunks;
--u;
size_t v = u % page_size + 1;
if (v < page_size / fanout) {
/* Fast path. Child is on the same page as the parent. */
v *= fanout - 1;
if (u > SIZE_MAX - 2 - v) {
/* Child overflow. */
return SIZE_MAX;
}
return u + v + 2;
}
/* Slow path. Child is on another page. */
const size_t page_leaves = (fanout - 1) * page_chunks + 1;
v += (u / page_size + 1) * page_leaves - page_size;
if (v > (SIZE_MAX - 1) / page_size) {
/* Child overflow. */
return SIZE_MAX;
}
return v * page_size + 1;
}
/* Returns a pointer to base[index]. */
static inline void *_gheap_get_item_ptr(const struct gheap_ctx *const ctx,
const void *const base, const size_t index)
{
const size_t item_size = ctx->item_size;
assert(index <= SIZE_MAX / item_size);
const size_t offset = item_size * index;
assert((uintptr_t)base <= UINTPTR_MAX - offset);
return ((char *)base) + offset;
}
/*
* Sifts the item up in the given sub-heap with the given root_index
* starting from the hole_index.
*/
static inline void _gheap_sift_up(const struct gheap_ctx *const ctx,
void *const base, const size_t root_index, size_t hole_index,
const void *const item)
{
assert(hole_index >= root_index);
const gheap_less_comparer_t less_comparer = ctx->less_comparer;
const void *const less_comparer_ctx = ctx->less_comparer_ctx;
const gheap_item_mover_t item_mover = ctx->item_mover;
while (hole_index > root_index) {
const size_t parent_index = gheap_get_parent_index(ctx, hole_index);
assert(parent_index >= root_index);
const void *const parent = _gheap_get_item_ptr(ctx, base, parent_index);
if (!less_comparer(less_comparer_ctx, parent, item)) {
break;
}
item_mover(_gheap_get_item_ptr(ctx, base, hole_index),
parent);
hole_index = parent_index;
}
item_mover(_gheap_get_item_ptr(ctx, base, hole_index), item);
}
/*
* Moves the max child into the given hole and returns index
* of the new hole.
*/
static inline size_t _gheap_move_up_max_child(const struct gheap_ctx *const ctx,
void *const base, const size_t children_count,
const size_t hole_index, const size_t child_index)
{
assert(children_count > 0);
assert(children_count <= ctx->fanout);
assert(child_index == gheap_get_child_index(ctx, hole_index));
const gheap_less_comparer_t less_comparer = ctx->less_comparer;
const void *const less_comparer_ctx = ctx->less_comparer_ctx;
const gheap_item_mover_t item_mover = ctx->item_mover;
size_t max_child_index = child_index;
for (size_t i = 1; i < children_count; ++i) {
if (!less_comparer(less_comparer_ctx,
_gheap_get_item_ptr(ctx, base, child_index + i),
_gheap_get_item_ptr(ctx, base, max_child_index))) {
max_child_index = child_index + i;
}
}
item_mover(_gheap_get_item_ptr(ctx, base, hole_index),
_gheap_get_item_ptr(ctx, base, max_child_index));
return max_child_index;
}
/*
* Sifts the given item down in the heap of the given size starting
* from the hole_index.
*/
static inline void _gheap_sift_down(const struct gheap_ctx *const ctx,
void *const base, const size_t heap_size, size_t hole_index,
const void *const item)
{
assert(heap_size > 0);
assert(hole_index < heap_size);
const size_t fanout = ctx->fanout;
const size_t root_index = hole_index;
const size_t last_full_index = heap_size - (heap_size - 1) % fanout;
while (1) {
const size_t child_index = gheap_get_child_index(ctx, hole_index);
if (child_index >= last_full_index) {
if (child_index < heap_size) {
assert(child_index == last_full_index);
hole_index = _gheap_move_up_max_child(ctx, base,
heap_size - child_index, hole_index, child_index);
}
break;
}
assert(heap_size - child_index >= fanout);
hole_index = _gheap_move_up_max_child(ctx, base, fanout, hole_index,
child_index);
}
_gheap_sift_up(ctx, base, root_index, hole_index, item);
}
/*
* Pops the maximum item from the heap [base[0] ... base[heap_size-1]]
* into base[heap_size].
*/
static inline void _gheap_pop_max_item(const struct gheap_ctx *const ctx,
void *const base, const size_t heap_size)
{
void *const hole = _gheap_get_item_ptr(ctx, base, heap_size);
gheap_swap_max_item(ctx, base, heap_size, hole);
}
static inline size_t gheap_is_heap_until(const struct gheap_ctx *const ctx,
const void *const base, const size_t heap_size)
{
const gheap_less_comparer_t less_comparer = ctx->less_comparer;
const void *const less_comparer_ctx = ctx->less_comparer_ctx;
for (size_t u = 1; u < heap_size; ++u) {
const size_t v = gheap_get_parent_index(ctx, u);
const void *const a = _gheap_get_item_ptr(ctx, base, v);
const void *const b = _gheap_get_item_ptr(ctx, base, u);
if (less_comparer(less_comparer_ctx, a, b)) {
return u;
}
}
return heap_size;
}
static inline int gheap_is_heap(const struct gheap_ctx *const ctx,
const void *const base, const size_t heap_size)
{
return (gheap_is_heap_until(ctx, base, heap_size) == heap_size);
}
static inline void gheap_make_heap(const struct gheap_ctx *const ctx,
void *const base, const size_t heap_size)
{
const size_t fanout = ctx->fanout;
const size_t page_chunks = ctx->page_chunks;
const size_t item_size = ctx->item_size;
const gheap_item_mover_t item_mover = ctx->item_mover;
if (heap_size > 1) {
/* Skip leaf nodes without children. This is easy to do for non-paged heap,
* i.e. when page_chunks = 1, but it is difficult for paged heaps.
* So leaf nodes in paged heaps are visited anyway.
*/
size_t i = (page_chunks == 1) ? ((heap_size - 2) / fanout) :
(heap_size - 2);
do {
char tmp[item_size];
item_mover(tmp, _gheap_get_item_ptr(ctx, base, i));
_gheap_sift_down(ctx, base, heap_size, i, tmp);
} while (i-- > 0);
}
assert(gheap_is_heap(ctx, base, heap_size));
}
static inline void gheap_push_heap(const struct gheap_ctx *const ctx,
void *const base, const size_t heap_size)
{
assert(heap_size > 0);
assert(gheap_is_heap(ctx, base, heap_size - 1));
const size_t item_size = ctx->item_size;
const gheap_item_mover_t item_mover = ctx->item_mover;
if (heap_size > 1) {
const size_t u = heap_size - 1;
char tmp[item_size];
item_mover(tmp, _gheap_get_item_ptr(ctx, base, u));
_gheap_sift_up(ctx, base, 0, u, tmp);
}
assert(gheap_is_heap(ctx, base, heap_size));
}
static inline void gheap_pop_heap(const struct gheap_ctx *const ctx,
void *const base, const size_t heap_size)
{
assert(heap_size > 0);
assert(gheap_is_heap(ctx, base, heap_size));
if (heap_size > 1) {
_gheap_pop_max_item(ctx, base, heap_size - 1);
}
assert(gheap_is_heap(ctx, base, heap_size - 1));
}
static inline void gheap_sort_heap(const struct gheap_ctx *const ctx,
void *const base, const size_t heap_size)
{
for (size_t i = heap_size; i > 1; --i) {
_gheap_pop_max_item(ctx, base, i - 1);
}
}
static inline void gheap_swap_max_item(const struct gheap_ctx *const ctx,
void *const base, const size_t heap_size, void *item)
{
assert(heap_size > 0);
assert(gheap_is_heap(ctx, base, heap_size));
const size_t item_size = ctx->item_size;
const gheap_item_mover_t item_mover = ctx->item_mover;
char tmp[item_size];
item_mover(tmp, item);
item_mover(item, base);
_gheap_sift_down(ctx, base, heap_size, 0, tmp);
assert(gheap_is_heap(ctx, base, heap_size));
}
static inline void gheap_restore_heap_after_item_increase(
const struct gheap_ctx *const ctx,
void *const base, const size_t heap_size, size_t modified_item_index)
{
assert(heap_size > 0);
assert(modified_item_index < heap_size);
assert(gheap_is_heap(ctx, base, modified_item_index));
const size_t item_size = ctx->item_size;
const gheap_item_mover_t item_mover = ctx->item_mover;
if (modified_item_index > 0) {
char tmp[item_size];
item_mover(tmp, _gheap_get_item_ptr(ctx, base, modified_item_index));
_gheap_sift_up(ctx, base, 0, modified_item_index, tmp);
}
assert(gheap_is_heap(ctx, base, heap_size));
(void)heap_size;
}
static inline void gheap_restore_heap_after_item_decrease(
const struct gheap_ctx *const ctx,
void *const base, const size_t heap_size, size_t modified_item_index)
{
assert(heap_size > 0);
assert(modified_item_index < heap_size);
assert(gheap_is_heap(ctx, base, modified_item_index));
const size_t item_size = ctx->item_size;
const gheap_item_mover_t item_mover = ctx->item_mover;
char tmp[item_size];
item_mover(tmp, _gheap_get_item_ptr(ctx, base, modified_item_index));
_gheap_sift_down(ctx, base, heap_size, modified_item_index, tmp);
assert(gheap_is_heap(ctx, base, heap_size));
}
static inline void gheap_remove_from_heap(const struct gheap_ctx *const ctx,
void *const base, const size_t heap_size, size_t item_index)
{
assert(heap_size > 0);
assert(item_index < heap_size);
assert(gheap_is_heap(ctx, base, heap_size));
const size_t item_size = ctx->item_size;
const gheap_less_comparer_t less_comparer = ctx->less_comparer;
const void *const less_comparer_ctx = ctx->less_comparer_ctx;
const gheap_item_mover_t item_mover = ctx->item_mover;
const size_t new_heap_size = heap_size - 1;
if (item_index < new_heap_size) {
char tmp[item_size];
void *const hole = _gheap_get_item_ptr(ctx, base, new_heap_size);
item_mover(tmp, hole);
item_mover(hole, _gheap_get_item_ptr(ctx, base, item_index));
if (less_comparer(less_comparer_ctx, tmp, hole)) {
_gheap_sift_down(ctx, base, new_heap_size, item_index, tmp);
}
else {
_gheap_sift_up(ctx, base, 0, item_index, tmp);
}
}
assert(gheap_is_heap(ctx, base, new_heap_size));
}
#endif