Commit 5da3b70b authored by Vladimír Čunát's avatar Vladimír Čunát

lib/generic/queue: a new efficient structure

It's focused on FIFO queue usage.

FIXME: unit tests
parent 0edcd0ad
......@@ -19,6 +19,12 @@ array
.. doxygenfile:: array.h
:project: libkres
queue
~~~~~
.. doxygenfile:: queue.h
:project: libkres
map
~~~
......
/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "lib/generic/queue.h"
#include <string.h>
KR_EXPORT void queue_init_impl(struct queue *q, size_t item_size)
{
q->len = 0;
q->item_size = item_size;
q->head = q->tail = NULL;
/* Take 128 B (two x86 cache lines), except a small margin
* that the allocator can use for its overhead.
* Normally (64-bit pointers) this means 16 B header + 13*8 B data. */
q->chunk_cap = ( ((ssize_t)128) - offsetof(struct queue_chunk, data)
- sizeof(size_t)
) / item_size;
if (!q->chunk_cap) q->chunk_cap = 1; /* item_size big enough by itself */
}
KR_EXPORT void queue_deinit_impl(struct queue *q)
{
assert(q);
struct queue_chunk *p = q->head;
while (p != NULL) {
struct queue_chunk *pf = p;
p = p->next;
free(pf);
}
#ifndef NDEBUG
memset(q, 0, sizeof(*q));
#endif
}
static struct queue_chunk * queue_chunk_new(const struct queue *q)
{
struct queue_chunk *c = malloc(offsetof(struct queue_chunk, data)
+ q->chunk_cap * q->item_size);
if (unlikely(!c)) abort(); // simplify stuff
memset(c, 0, offsetof(struct queue_chunk, data));
c->cap = q->chunk_cap;
/* ->begin and ->end are zero, i.e. we optimize for _push
* and not _push_head, by default. */
return c;
}
/* Return pointer to the space for the new element. */
KR_EXPORT void * queue_push_impl(struct queue *q)
{
assert(q);
struct queue_chunk *t = q->tail; // shorthand
if (unlikely(!t)) {
assert(!q->head && !q->len);
q->head = q->tail = t = queue_chunk_new(q);
} else
if (t->end == t->cap) {
if (t->begin * 2 >= t->cap) {
/* Utilization is below 50%, so let's shift (no overlap). */
memcpy(t->data, t->data + t->begin * q->item_size,
(t->end - t->begin) * q->item_size);
t->end -= t->begin;
t->begin = 0;
} else {
/* Let's grow the tail by another chunk. */
assert(!t->next);
t->next = queue_chunk_new(q);
t = q->tail = t->next;
}
}
assert(t->end < t->cap);
++(q->len);
++(t->end);
return t->data + q->item_size * (t->end - 1);
}
/* Return pointer to the space for the new element. */
KR_EXPORT void * queue_push_head_impl(struct queue *q)
{
/* When we have choice, we optimize for further _push_head,
* i.e. when shifting or allocating a chunk,
* we store items on the tail-end of the chunk. */
assert(q);
struct queue_chunk *h = q->head; // shorthand
if (unlikely(!h)) {
assert(!q->tail && !q->len);
h = q->head = q->tail = queue_chunk_new(q);
h->begin = h->end = h->cap;
} else
if (h->begin == 0) {
if (h->end * 2 <= h->cap) {
/* Utilization is below 50%, so let's shift (no overlap).
* Computations here are simplified due to h->begin == 0. */
const int cnt = h->end;
memcpy(h->data + (h->cap - cnt) * q->item_size, h->data,
cnt * q->item_size);
h->begin = h->cap - cnt;
h->end = h->cap;
} else {
/* Let's grow the head by another chunk. */
h = queue_chunk_new(q);
h->next = q->head;
q->head = h;
h->begin = h->end = h->cap;
}
}
assert(h->begin > 0);
--(h->begin);
++(q->len);
return h->data + q->item_size * h->begin;
}
/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file queue.h
* @brief A queue, usable for FIFO and LIFO simultaneously.
*
* FIXME: unit tests
*
* Both the head and tail of the queue can be accessed and pushed to,
* but only the head can be popped from.
*
* @note The implementation uses a singly linked list of blocks
* where each block stores an array of values (for better efficiency).
*
* Example usage:
* @code{.c}
// define new queue type, and init a new queue instance
typedef queue_t(int) queue_int_t;
queue_int_t q;
queue_init(q);
// do some operations
queue_push(q, 1);
queue_push(q, 2);
queue_push(q, 3);
queue_push(q, 4);
queue_pop(q);
assert(queue_head(q) == 2);
assert(queue_tail(q) == 4);
queue_push_head(q, 0);
++queue_tail(q);
assert(queue_tail(q) == 5);
// free it up
queue_deinit(q);
// you may use dynamic allocation for the type itself
queue_int_t *qm = malloc(sizeof(queue_int_t));
queue_init(*qm);
queue_deinit(*qm);
free(qm);
* @endcode
*
* \addtogroup generics
* @{
*/
#pragma once
#include "lib/defines.h"
#include "contrib/ucw/lib.h"
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
/** @brief The type for queue, parametrized by value type. */
#define queue_t(type) \
union { \
type *pdata_t; /* only the *type* information is used */ \
struct queue queue; \
}
/** @brief Initialize a queue. You can malloc() it the usual way. */
#define queue_init(q) do { \
(void)(((__typeof__(((q).pdata_t)))0) == (void *)0); /* typecheck queue_t */ \
queue_init_impl(&(q).queue, sizeof(*(q).pdata_t)); \
} while (false)
/** @brief De-initialize a queue: make it invalid and free any inner allocations. */
#define queue_deinit(q) \
queue_deinit_impl(&(q).queue)
/** @brief Push data to queue's tail. (Type-safe version; use _impl() otherwise.) */
#define queue_push(q, data) \
*((__typeof__((q).pdata_t)) queue_push_impl(&(q).queue)) = data
/** @brief Push data to queue's head. (Type-safe version; use _impl() otherwise.) */
#define queue_push_head(q, data) \
*((__typeof__((q).pdata_t)) queue_push_head_impl(&(q).queue)) = data
/** @brief Remove the element at the head. */
#define queue_pop(q) \
queue_pop_impl(&(q).queue)
/** @brief Return a "reference" to the element at the head (it's an L-value) . */
#define queue_head(q) \
( *(__typeof__((q).pdata_t)) queue_head_impl(&(q).queue) )
/** @brief Return a "reference" to the element at the tail (it's an L-value) . */
#define queue_tail(q) \
( *(__typeof__((q).pdata_t)) queue_tail_impl(&(q).queue) )
/** @brief Return the number of elements in the queue. */
#define queue_len(q) \
((const size_t)(q).queue.len)
/* ====================== Internal for the implementation ================== */
/** @cond internal */
struct queue;
/* Non-inline functions are exported to be usable from daemon. */
void queue_init_impl(struct queue *q, size_t item_size);
void queue_deinit_impl(struct queue *q);
void * queue_push_impl(struct queue *q);
void * queue_push_head_impl(struct queue *q);
struct queue_chunk;
struct queue {
size_t len;
uint16_t chunk_cap, item_size;
struct queue_chunk *head, *tail;
};
struct queue_chunk {
struct queue_chunk *next; /*< head -> ... -> tail */
int16_t begin, end, cap, pad_; /*< indices: zero is closest to head */
/*< We could fit into uint8_t for example, but the choice of (3+1)*2 bytes
* is a compromise between wasting space and getting a good alignment.
* In particular, queue_t(type*) will store the pointers on addresses
* aligned to the pointer size, in both 64-bit and 32-bit platforms.
*/
char data[];
/**< The item data. We use "char" to satisfy the C99+ aliasing rules.
* See C99 section 6.5 Expressions, paragraph 7.
* Any type can be accessed through char-pointer,
* so we can use a common struct definition
* for all types being held.
*/
};
static inline void * queue_head_impl(const struct queue *q)
{
assert(q);
struct queue_chunk *h = q->head;
if (unlikely(!h))
return NULL;
assert(h->end > h->begin);
return h->data + h->begin * q->item_size;
}
static inline void * queue_tail_impl(const struct queue *q)
{
assert(q);
struct queue_chunk *t = q->tail;
if (unlikely(!t))
return NULL;
assert(t->end > t->begin);
return t->data + (t->end - 1) * q->item_size;
}
static inline void queue_pop_impl(struct queue *q)
{
assert(q);
struct queue_chunk *h = q->head;
assert(h && h->end > h->begin);
if (h->end - h->begin == 1) {
/* removing the last element in the chunk */
q->head = h->next;
free(h);
} else {
++(h->begin);
}
--(q->len);
}
/** @endcond (internal) */
/** @} (addtogroup generics) */
......@@ -15,6 +15,7 @@ libkres_SOURCES := \
lib/dnssec/ta.c \
lib/generic/lru.c \
lib/generic/map.c \
lib/generic/queue.c \
lib/generic/trie.c \
lib/layer/cache.c \
lib/layer/iterate.c \
......@@ -41,6 +42,7 @@ libkres_HEADERS := \
lib/generic/lru.h \
lib/generic/map.h \
lib/generic/pack.h \
lib/generic/queue.h \
lib/generic/trie.h \
lib/layer.h \
lib/layer/iterate.h \
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment