From: Bing Zhao <bingz@mellanox.com>
To: yipeng1.wang@intel.com, sameh.gobriel@intel.com,
bruce.richardson@intel.com, pablo.de.lara.guarch@intel.com
Cc: dev@dpdk.org, bingz@mellanox.com
Subject: [dpdk-dev] [RFC] rte_hash: introduce hash list into hash lib
Date: Wed, 28 Aug 2019 14:51:49 +0800 [thread overview]
Message-ID: <1566975109-318949-2-git-send-email-bingz@mellanox.com> (raw)
In-Reply-To: <1566975109-318949-1-git-send-email-bingz@mellanox.com>
An extendible hash list library supporting add, delete, lookup action.
Extending when the elements number of any list reach the pre-set max
value, and splitting of the list is done passively when this list is
being accessed(add, del, lookup). Right now, it is not a multi-thread
safe implementation and no table size shrinking is supported.
Signed-off-by: Bing Zhao <bingz@mellanox.com>
---
lib/librte_hash/Makefile | 2 +
lib/librte_hash/rte_hash_version.map | 15 +
lib/librte_hash/rte_hlist.c | 687 +++++++++++++++++++++++++++++++++++
lib/librte_hash/rte_hlist.h | 563 ++++++++++++++++++++++++++++
4 files changed, 1267 insertions(+)
create mode 100644 lib/librte_hash/rte_hlist.c
create mode 100644 lib/librte_hash/rte_hlist.h
diff --git a/lib/librte_hash/Makefile b/lib/librte_hash/Makefile
index c8c435d..6de49dd 100644
--- a/lib/librte_hash/Makefile
+++ b/lib/librte_hash/Makefile
@@ -17,6 +17,7 @@ LIBABIVER := 2
# all source are stored in SRCS-y
SRCS-$(CONFIG_RTE_LIBRTE_HASH) := rte_cuckoo_hash.c
SRCS-$(CONFIG_RTE_LIBRTE_HASH) += rte_fbk_hash.c
+SRCS-$(CONFIG_RTE_LIBRTE_HASH) += rte_hlist.c
# install this header file
SYMLINK-$(CONFIG_RTE_LIBRTE_HASH)-include := rte_hash.h
@@ -29,5 +30,6 @@ endif
SYMLINK-$(CONFIG_RTE_LIBRTE_HASH)-include += rte_jhash.h
SYMLINK-$(CONFIG_RTE_LIBRTE_HASH)-include += rte_thash.h
SYMLINK-$(CONFIG_RTE_LIBRTE_HASH)-include += rte_fbk_hash.h
+SYMLINK-$(CONFIG_RTE_LIBRTE_HASH)-include += rte_hlist.h
include $(RTE_SDK)/mk/rte.lib.mk
diff --git a/lib/librte_hash/rte_hash_version.map b/lib/librte_hash/rte_hash_version.map
index 734ae28..9325f4f 100644
--- a/lib/librte_hash/rte_hash_version.map
+++ b/lib/librte_hash/rte_hash_version.map
@@ -59,4 +59,19 @@ EXPERIMENTAL {
rte_hash_free_key_with_position;
+ rte_hlist_add_key;
+ rte_hlist_add_key_data;
+ rte_hlist_add_key_data_len;
+ rte_hlist_add_key_with_hash_data;
+ rte_hlist_add_key_with_hash_data_len;
+ rte_hlist_clear_all_entries_with_cb;
+ rte_hlist_create;
+ rte_hlist_del_entry_fast_return_data;
+ rte_hlist_del_key;
+ rte_hlist_del_key_return_data;
+ rte_hlist_free;
+ rte_hlist_hash;
+ rte_hlist_lookup;
+ rte_hlist_lookup_with_hash;
+
};
diff --git a/lib/librte_hash/rte_hlist.c b/lib/librte_hash/rte_hlist.c
new file mode 100644
index 0000000..d2e96fe
--- /dev/null
+++ b/lib/librte_hash/rte_hlist.c
@@ -0,0 +1,687 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2019 Mellanox Technologies, Ltd
+ */
+
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_eal_memconfig.h>
+#include <rte_malloc.h>
+#include <rte_memcpy.h>
+#include <rte_errno.h>
+#include <rte_log.h>
+
+#include "rte_hlist.h"
+
+TAILQ_HEAD(rte_hlist_list, rte_tailq_entry);
+
+static struct rte_tailq_elem rte_hlist_tailq = {
+ .name = "RTE_HLIST",
+};
+EAL_REGISTER_TAILQ(rte_hlist_tailq)
+
+hash_sig_t
+rte_hlist_hash(const struct rte_hlist_table *h, const void *key)
+{
+ /* calc hash result by key */
+ return h->hash_func(key, h->key_len, h->init_val);
+}
+
+static inline int
+__rte_hlist_extend_list_table(struct rte_hlist_table *h)
+{
+ struct rte_hlist_head_entry *new;
+ uint32_t dsize = (h->bucket_mask + 1) << 1;
+
+ new = (struct rte_hlist_head_entry *)rte_realloc(h->t,
+ sizeof(struct rte_hlist_head_entry) * dsize, 0);
+ if (new == NULL) {
+ rte_errno = ENOMEM;
+ return -rte_errno;
+ }
+ memset(((char *)new)+sizeof(struct rte_hlist_head_entry)*(dsize>>1),
+ 0, sizeof(struct rte_hlist_head_entry)*(dsize>>1));
+
+ /* new head that first element prev points to must be adjusted */
+ h->t = new;
+ h->entries *= 2;
+ h->bucket_shift += 1;
+ h->bucket_mask = dsize - 1;
+
+ return 0;
+}
+
+static inline uint32_t
+__rte_hlist_find_pre_unsplited_list(
+ const struct rte_hlist_table *h, uint32_t idx)
+{
+ struct rte_hlist_head_entry *n_he;
+ uint32_t gap = (h->bucket_shift > h->t[idx].bucket_shift) ?
+ (h->bucket_shift - h->t[idx].bucket_shift) : 0;
+ uint32_t i;
+ uint32_t p_idx = idx;
+
+ /* If the shift number is not zero, just return the one from input. */
+ for (i = 0; i <= gap; i++) {
+ p_idx = idx &
+ HLIST_CALC_PREVIOUS_BUCKET_MASK(h->bucket_mask, i);
+ n_he = &h->t[p_idx];
+ if (n_he->bucket_shift != 0)
+ break;
+ }
+
+ return p_idx;
+}
+
+static inline int
+__rte_hlist_split_one_list(const struct rte_hlist_table *h,
+ uint32_t idx)
+{
+ struct rte_hlist_node_entry *pos;
+ struct rte_hlist_node_entry *tp;
+ struct rte_hlist_head_entry *he = &h->t[idx];
+ struct rte_hlist_head_entry *n_he;
+ uint32_t new_idx;
+ uint32_t sh;
+ uint32_t sh_gap_mul;
+ uint32_t i = 0;
+
+ if (h->bucket_shift <= he->bucket_shift) {
+ rte_errno = EINVAL;
+ return -rte_errno;
+ }
+
+ /* adjust the '(elm)->field.le_prev' of first element to the new head */
+ LIST_MOVE_TO_NEW_HEAD(&he->head, &he->head, next);
+
+ /* TAILQ is easy for lists combining when shrinking the table, but a
+ * little more memory will be costed for each head. No need to
+ * re-calculate the hash value.
+ */
+ LIST_FOREACH_SAFE(pos, &he->head, next, tp) {
+ new_idx = pos->d.sig & h->bucket_mask;
+ if (new_idx != idx) {
+ LIST_REMOVE(pos, next);
+ he->entries_in_bucket--;
+ n_he = &h->t[new_idx];
+ LIST_INSERT_HEAD(&n_he->head, pos, next);
+ n_he->entries_in_bucket++;
+ }
+ }
+
+ /* old_mask to speed up:
+ * [0, old_mask+1, 2(old_mask+1) ... <mask] | idx
+ */
+ do { i++; } while (h->bucket_mask >> i);
+ sh = i - (h->bucket_shift - he->bucket_shift);
+ sh_gap_mul = 1 << (h->bucket_shift - he->bucket_shift);
+ for (i = 0; i < sh_gap_mul; i++) {
+ new_idx = (i << sh) | idx;
+ h->t[new_idx].bucket_shift = h->bucket_shift;
+ }
+
+ return 0;
+}
+
+static inline int
+__rte_hlist_find_node_entry(struct rte_hlist_table *h,
+ const void *key, hash_sig_t sig, struct rte_hlist_node_entry **p)
+{
+ uint32_t idx;
+ uint32_t n_idx;
+ struct rte_hlist_head_entry *he;
+ struct rte_hlist_head_entry *n_he;
+ struct rte_hlist_node_entry *pos;
+ uint32_t matched = FALSE;
+ int ret;
+
+ if ((h == NULL) || (key == NULL) || (p == NULL)) {
+ rte_errno = EINVAL;
+ return -rte_errno;
+ }
+
+ idx = sig & h->bucket_mask;
+ he = &h->t[idx];
+
+ /* When the entries linked to the list reach upper limit, we try to
+ * extend the length of the head array. Then we just split this list.
+ * Others will be checked and splitted when being accessed.
+ * Shift number also needs to be checked in case of extra extending.
+ */
+ if ((he->entries_in_bucket > h->entries_per_bucket) &&
+ (he->bucket_shift == h->bucket_shift)) {
+ /* If the list operation failed, it returns nothing even if
+ * the key will match one of the existing entries.
+ */
+ ret = __rte_hlist_extend_list_table(h);
+ if (ret < 0) {
+ RTE_LOG(ERR, HASH,
+ "Failed to extend the list table %d\n", ret);
+ goto exit;
+ }
+ }
+
+ if (he->bucket_shift < h->bucket_shift) {
+ uint32_t p_idx = __rte_hlist_find_pre_unsplited_list(h, idx);
+
+ /* No matter how many times extending is done, one splitting
+ * is enough. If shift is 0, then the 'oldest' list that is
+ * not splitted should be splitted(no matter if any entry in).
+ * If not zero, just try to split this list and try to move
+ * entries into the new one.
+ */
+ ret = __rte_hlist_split_one_list(h, p_idx);
+ if (ret < 0) {
+ RTE_LOG(ERR, HASH,
+ "Failed to split the bucket %d\n", ret);
+ goto exit;
+ }
+ }
+
+ /* Need to re-do since the mask & pointer may change if extended */
+ n_idx = sig & h->bucket_mask;
+ n_he = &h->t[n_idx];
+
+ /* Passive way: just split the list that being accessed.
+ * After splitting, only one list needs to be traversed.
+ */
+ if (n_he->entries_in_bucket > 0) {
+ LIST_FOREACH(pos, &n_he->head, next) {
+ if (pos->d.sig == sig)
+ /* Key comparison needs be optimized later */
+ if (!memcmp(pos->key, key, h->key_len)) {
+ matched = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (matched == TRUE) {
+ *p = pos;
+ /* The head index will always be the calculated one
+ * because of the splitting of the list.
+ */
+ ret = n_idx;
+ } else {
+ *p = NULL;
+ ret = -ENOENT;
+ }
+
+exit:
+ return ret;
+}
+
+static inline struct rte_hlist_data_element *
+__rte_hlist_add_key_with_hash_data(struct rte_hlist_table *h,
+ const void *key, hash_sig_t sig, void *data, uint16_t len, uint8_t cus)
+{
+ int idx;
+ struct rte_hlist_head_entry *he;
+ struct rte_hlist_node_entry *pos;
+
+ idx = __rte_hlist_find_node_entry(h, key, sig, &pos);
+ if (idx >= 0) {
+ pos->d.flags &= (~HLIST_DATA_NEW_ENTRY);
+ return &pos->d;
+ } else if ((idx < 0) && (idx != -ENOENT))
+ return NULL;
+
+ /* All the fields will be written */
+ pos = rte_malloc(NULL,
+ sizeof(struct rte_hlist_node_entry) + h->key_len, 0);
+ if (pos == NULL) {
+ RTE_LOG(ERR, HASH, "Failed to allocate new list node\n");
+ return NULL;
+ }
+
+ pos->d.sig = sig;
+ /* should be optimized if the key length is small */
+ rte_memcpy(pos->key, key, h->key_len);
+ /* User should be responsible for the data no matter how many bytes
+ * the length is.
+ */
+ if (cus == TRUE) {
+ pos->d.extra_data = data;
+ pos->d.flags = HLIST_DATA_CUSTOM_EXTRA_DATA;
+ h->custom_entries++;
+ } else {
+ if ((data != NULL) && (len != 0)) {
+ pos->d.flags = HLIST_DATA_INLINE;
+ switch (len) {
+ case 1:
+ pos->d.v8 = *(uint8_t *)data;
+ break;
+ case 2:
+ pos->d.v16 = *(uint16_t *)data;
+ break;
+ case 4:
+ pos->d.v32 = *(uint32_t *)data;
+ break;
+ case 8:
+ pos->d.v64 = *(uint64_t *)data;
+ break;
+ default:
+ pos->d.extra_data = rte_malloc(NULL, len, 0);
+ if (pos->d.extra_data == NULL) {
+ rte_free(pos);
+ rte_errno = ENOMEM;
+ return NULL;
+ }
+ rte_memcpy(pos->d.extra_data, data, len);
+ pos->d.flags = HLIST_DATA_ALLOC_WITH_SIZE(len);
+ break;
+ }
+ } else {
+ pos->d.extra_data = data;
+ pos->d.flags = HLIST_DATA_NOT_EXIST;
+ }
+ }
+ pos->d.flags |= HLIST_DATA_NEW_ENTRY;
+ idx = sig & h->bucket_mask;
+ he = &h->t[idx];
+ LIST_INSERT_HEAD(&he->head, pos, next);
+ he->entries_in_bucket++;
+
+ return &pos->d;
+}
+
+struct rte_hlist_data_element *
+rte_hlist_add_key(struct rte_hlist_table *h, const void *key)
+{
+ RETURN_IF_TRUE_ERRNO(((h == NULL) || (key == NULL)), NULL, -EINVAL);
+
+ return __rte_hlist_add_key_with_hash_data(h, key,
+ rte_hlist_hash(h, key), NULL, 0, FALSE);
+}
+
+struct rte_hlist_data_element *
+rte_hlist_add_key_data(struct rte_hlist_table *h,
+ const void *key, void *data)
+{
+ RETURN_IF_TRUE_ERRNO(((h == NULL) || (key == NULL)), NULL, -EINVAL);
+
+ return __rte_hlist_add_key_with_hash_data(h, key,
+ rte_hlist_hash(h, key), data, 0, TRUE);
+}
+
+struct rte_hlist_data_element *
+rte_hlist_add_key_data_len(struct rte_hlist_table *h,
+ const void *key, void *data, uint16_t len, uint8_t cus)
+{
+ RETURN_IF_TRUE_ERRNO(((h == NULL) || (key == NULL)), NULL, -EINVAL);
+
+ return __rte_hlist_add_key_with_hash_data(h, key,
+ rte_hlist_hash(h, key), data, len, cus);
+}
+
+struct rte_hlist_data_element *
+rte_hlist_add_key_with_hash_data(struct rte_hlist_table *h,
+ const void *key, hash_sig_t sig, void *data)
+{
+ RETURN_IF_TRUE_ERRNO(((h == NULL) || (key == NULL)), NULL, -EINVAL);
+
+ return __rte_hlist_add_key_with_hash_data(h, key, sig, data, 0, TRUE);
+}
+
+struct rte_hlist_data_element *
+rte_hlist_add_key_with_hash_data_len(
+ struct rte_hlist_table *h, const void *key, hash_sig_t sig,
+ void *data, uint16_t len, uint8_t cus)
+{
+ RETURN_IF_TRUE_ERRNO(((h == NULL) || (key == NULL)), NULL, -EINVAL);
+
+ return __rte_hlist_add_key_with_hash_data(h, key, sig, data, len, cus);
+}
+
+static inline int
+__rte_hlist_del_key_with_hash_return_data(
+ struct rte_hlist_table *h, const void *key,
+ hash_sig_t sig, void **data)
+{
+ struct rte_hlist_node_entry *pos;
+ int idx = __rte_hlist_find_node_entry(h, key, sig, &pos);
+ if (idx < 0)
+ return idx;
+
+ *data = NULL;
+ if (HLIST_DATA_CUSTOM_EXTRA_DATA & pos->d.flags) {
+ *data = pos->d.extra_data;
+ h->custom_entries--;
+ } else if (HLIST_DATA_IS_ALLOCATED(&pos->d)) {
+ rte_free(pos->d.extra_data);
+ }
+
+ LIST_REMOVE(pos, next);
+ rte_free(pos);
+ h->t[idx].entries_in_bucket--;
+
+ return 0;
+}
+
+static inline int
+__rte_hlist_del_key_with_hash(struct rte_hlist_table *h,
+ const void *key, hash_sig_t sig)
+{
+ struct rte_hlist_node_entry *pos;
+ int idx;
+
+ /* Currently there will be some 'false positive' to extend the lists
+ * head and split this list. e.g. only one more entry in the list to
+ * be moved to another list after extending and if it is the one to be
+ * removed, then there will be no entry in the 'brother' list after
+ * deletion. But the length of the lists head array is extended after
+ * searching. It is not a bug but not graceful enough right now.
+ * (Other hash table may also face some issues with this scenario)
+ */
+ idx = __rte_hlist_find_node_entry(h, key, sig, &pos);
+ if (idx < 0)
+ return idx;
+
+ if (HLIST_DATA_CUSTOM_EXTRA_DATA & pos->d.flags) {
+ if (h->free_func == NULL) {
+ rte_errno = EBUSY;
+ return -rte_errno;
+ }
+ h->free_func(pos->d.extra_data);
+ h->custom_entries--;
+ } else if (HLIST_DATA_IS_ALLOCATED(&pos->d)) {
+ rte_free(pos->d.extra_data);
+ }
+
+ LIST_REMOVE(pos, next);
+ rte_free(pos);
+ h->t[idx].entries_in_bucket--;
+
+ return 0;
+}
+
+static inline int
+__rte_hlist_del_entry_fast_return_data(struct rte_hlist_table *h,
+ struct rte_hlist_data_element *de, void **data)
+{
+ struct rte_hlist_node_entry *pos;
+ struct rte_hlist_head_entry *he;
+ uint32_t idx = de->sig & h->bucket_mask;
+ int ret;
+
+ pos = container_of(de, struct rte_hlist_node_entry, d);
+ he = &h->t[idx];
+
+ /* Splitting will ensure that the head and the first element pointers
+ * are consistent, or else the deletion will cause memory corruption.
+ */
+ if (he->bucket_shift < h->bucket_shift) {
+ uint32_t p_idx = __rte_hlist_find_pre_unsplited_list(h, idx);
+
+ ret = __rte_hlist_split_one_list(h, p_idx);
+ if (ret < 0) {
+ RTE_LOG(ERR, HASH,
+ "Failed to split the bucket %d\n", ret);
+ return ret;
+ }
+ }
+
+ if (HLIST_DATA_CUSTOM_EXTRA_DATA & pos->d.flags) {
+ *data = pos->d.extra_data;
+ h->custom_entries--;
+ } else if (HLIST_DATA_IS_ALLOCATED(&pos->d)) {
+ rte_free(pos->d.extra_data);
+ }
+ LIST_REMOVE(pos, next);
+ rte_free(pos);
+ he->entries_in_bucket--;
+
+ return 0;
+}
+
+int
+rte_hlist_del_key(struct rte_hlist_table *h, const void *key)
+{
+ RETURN_IF_TRUE(((h == NULL) || (key == NULL)), -EINVAL);
+
+ return __rte_hlist_del_key_with_hash(h, key, rte_hlist_hash(h, key));
+}
+
+int
+rte_hlist_del_key_return_data(struct rte_hlist_table *h,
+ const void *key, void **data)
+{
+ RETURN_IF_TRUE(((h == NULL) || (key == NULL) || (data == NULL)),
+ -EINVAL);
+
+ return __rte_hlist_del_key_with_hash_return_data(h, key,
+ rte_hlist_hash(h, key), data);
+}
+
+int
+rte_hlist_del_entry_fast_return_data(struct rte_hlist_table *h,
+ struct rte_hlist_data_element *de, void **data)
+{
+ RETURN_IF_TRUE(((h == NULL) || (key == NULL) || (data == NULL)),
+ -EINVAL);
+
+ return __rte_hlist_del_entry_fast_return_data(h, de, data);
+}
+
+struct rte_hlist_data_element *
+rte_hlist_lookup_with_hash(struct rte_hlist_table *h,
+ const void *key, hash_sig_t sig)
+{
+ struct rte_hlist_node_entry *p;
+ int idx;
+
+ idx = __rte_hlist_find_node_entry(h, key, sig, &p);
+ if (idx < 0)
+ return NULL;
+
+ return &p->d;
+}
+
+struct rte_hlist_data_element *
+rte_hlist_lookup(struct rte_hlist_table *h, const void *key)
+{
+ RETURN_IF_TRUE_ERRNO(((h == NULL) || (key == NULL)), NULL, -EINVAL);
+
+ return rte_hlist_lookup_with_hash(h, key, rte_hlist_hash(h, key));
+}
+
+static inline int
+__rte_hlist_clear_all_entries(struct rte_hlist_table *h, uint8_t force)
+{
+ uint32_t size;
+ uint32_t i;
+ struct rte_hlist_head_entry *he;
+ struct rte_hlist_node_entry *pos;
+ struct rte_hlist_node_entry *tp;
+
+ if (h == NULL) {
+ rte_errno = EINVAL;
+ return -rte_errno;
+ }
+
+ if ((force == FALSE) && (h->custom_entries) && (h->free_func == NULL)) {
+ rte_errno = EBUSY;
+ return -rte_errno;
+ }
+
+ size = h->bucket_mask + 1;
+ for (i = 0; i < size; i++) {
+ he = &h->t[i];
+ if (he->entries_in_bucket > 0) {
+ LIST_MOVE_TO_NEW_HEAD(&he->head, &he->head, next);
+ LIST_FOREACH_SAFE(pos, &he->head, next, tp) {
+ if (HLIST_DATA_IS_ALLOCATED(&pos->d)) {
+ rte_free(pos->d.extra_data);
+ } else if (HLIST_DATA_CUSTOM_EXTRA_DATA &
+ pos->d.flags) {
+ h->custom_entries--;
+ if (h->free_func != NULL)
+ h->free_func(
+ pos->d.extra_data);
+ }
+ LIST_REMOVE(pos, next);
+ rte_free(pos);
+ he->entries_in_bucket--;
+ }
+ }
+ }
+
+ return 0;
+}
+
+int
+rte_hlist_clear_all_entries_with_cb(struct rte_hlist_table *h,
+ rte_hlist_free_fn fn)
+{
+ rte_hlist_free_fn saved_fn;
+ int ret;
+
+ RETURN_IF_TRUE((h == NULL), -EINVAL);
+ saved_fn = h->free_func;
+ h->free_func = fn;
+ ret = __rte_hlist_clear_all_entries(h, TRUE);
+ h->free_func = saved_fn;
+
+ return ret;
+}
+
+struct rte_hlist_table *
+rte_hlist_create(const struct rte_hlist_params *params)
+{
+ struct rte_hlist_table *ht = NULL;
+ struct rte_tailq_entry *te;
+ char hash_name[RTE_HLIST_NAMESIZE];
+ struct rte_hlist_list *hlist_list;
+ uint32_t table_size;
+
+ hlist_list = RTE_TAILQ_CAST(rte_hlist_tailq.head,
+ rte_hlist_list);
+
+ /* Error checking of parameters. */
+ if ((!rte_is_power_of_2(params->entries)) ||
+ (!rte_is_power_of_2(params->entries_per_bucket)) ||
+ (params->entries == 0) ||
+ (params->entries_per_bucket == 0) ||
+ (params->entries_per_bucket > params->entries) ||
+ (params->entries > RTE_HLIST_ENTRIES_MAX) ||
+ (params->entries_per_bucket >
+ RTE_HLIST_ENTRIES_PER_BUCKET_MAX)) {
+ rte_errno = EINVAL;
+ return NULL;
+ }
+
+ snprintf(hash_name, sizeof(hash_name), "HLIST_%s", params->name);
+
+ rte_rwlock_write_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ /* guarantee there's no existing */
+ TAILQ_FOREACH(te, hlist_list, next) {
+ ht = (struct rte_hlist_table *)te->data;
+ if (strncmp(params->name, ht->name, RTE_HLIST_NAMESIZE) == 0)
+ break;
+ }
+ ht = NULL;
+ if (te != NULL) {
+ rte_errno = EEXIST;
+ goto exit;
+ }
+
+ te = rte_zmalloc("HLIST_TAILQ_ENTRY",
+ sizeof(struct rte_tailq_entry), 0);
+ if (te == NULL) {
+ RTE_LOG(ERR, HASH,
+ "Failed to allocate tailq entry for hlist\n");
+ goto exit;
+ }
+
+ /* Allocate memory for table. */
+ ht = (struct rte_hlist_table *)rte_zmalloc_socket(hash_name,
+ sizeof(struct rte_hlist_table), 0, params->socket_id);
+ if (ht == NULL) {
+ RTE_LOG(ERR, HASH, "Failed to allocate hlist table\n");
+ rte_free(te);
+ goto exit;
+ }
+
+ table_size = params->entries / params->entries_per_bucket;
+ ht->t = rte_zmalloc_socket(NULL,
+ sizeof(struct rte_hlist_head_entry) * table_size,
+ 0, params->socket_id);
+ if (ht->t == NULL) {
+ RTE_LOG(ERR, HASH, "Failed to allocate hlist head table\n");
+ rte_free(te);
+ rte_free(ht);
+ goto exit;
+ }
+
+ /* Because HLIST_HEAD_INIT is to initialize the value to zero, skip it
+ * here to accelerate the initialization stage.
+ */
+
+ /* Set up hash table context. */
+ snprintf(ht->name, sizeof(ht->name), "%s", params->name);
+ ht->entries = params->entries;
+ ht->entries_per_bucket = params->entries_per_bucket;
+ /* since table size is a power of 2 */
+ ht->bucket_mask = table_size - 1;
+ ht->key_len = params->key_len;
+
+ if (params->hash_func != NULL) {
+ ht->hash_func = params->hash_func;
+ ht->init_val = params->init_val;
+ } else {
+ ht->hash_func = RTE_HLIST_FUNC_DEFAULT;
+ ht->init_val = RTE_HLIST_INIT_VAL_DEFAULT;
+ }
+
+ /* not mandatory for the free function */
+ ht->free_func = params->free_func;
+
+ te->data = (void *)ht;
+
+ TAILQ_INSERT_TAIL(hlist_list, te, next);
+
+exit:
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ return ht;
+}
+
+int rte_hlist_free(struct rte_hlist_table *h)
+{
+ struct rte_tailq_entry *te;
+ struct rte_hlist_list *hlist_list;
+
+ if (h == NULL) {
+ rte_errno = EINVAL;
+ return -rte_errno;
+ }
+
+ hlist_list = RTE_TAILQ_CAST(rte_hlist_tailq.head, rte_hlist_list);
+
+ rte_rwlock_write_lock(RTE_EAL_TAILQ_RWLOCK);
+
+ /* find out tailq entry */
+ TAILQ_FOREACH(te, hlist_list, next) {
+ if (te->data == (void *)h)
+ break;
+ }
+
+ if (te == NULL) {
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ rte_errno = EEXIST;
+ return -rte_errno;
+ }
+
+ TAILQ_REMOVE(hlist_list, te, next);
+
+ rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+ (void)__rte_hlist_clear_all_entries(h, TRUE);
+ rte_free(h);
+ rte_free(te);
+
+ return 0;
+}
+
diff --git a/lib/librte_hash/rte_hlist.h b/lib/librte_hash/rte_hlist.h
new file mode 100644
index 0000000..8fd1b6a
--- /dev/null
+++ b/lib/librte_hash/rte_hlist.h
@@ -0,0 +1,563 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2019 Mellanox Technologies, Ltd
+ */
+
+#ifndef _RTE_HLIST_H_
+#define _RTE_HLIST_H_
+
+/**
+ * @file
+ *
+ * RTE Hash List
+ */
+
+#include <stdint.h>
+#include <errno.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <rte_config.h>
+#ifndef RTE_HLIST_FUNC_DEFAULT
+#if defined(RTE_ARCH_X86) || defined(RTE_MACHINE_CPUFLAG_CRC32)
+#include <rte_hash_crc.h>
+/** Default hlist hash function if none is specified. */
+#define RTE_HLIST_FUNC_DEFAULT rte_hash_crc
+#else
+#include <rte_jhash.h>
+#define RTE_HLIST_FUNC_DEFAULT rte_jhash
+#endif
+#endif
+
+/* To enable the deletion when iterating the list */
+#ifndef LIST_FOREACH_SAFE
+#define LIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = ((head)->lh_first); \
+ (var) && ((tvar) = ((var)->field.le_next), 1); \
+ (var) = (tvar))
+#endif
+
+/* To move the whole list from one head to another */
+#define LIST_MOVE_TO_NEW_HEAD(new, old, field) do { \
+ (new)->lh_first = (old)->lh_first; \
+ if (((new)->lh_first) != NULL) \
+ (new)->lh_first->field.le_prev = &(new)->lh_first; \
+} while (/*CONSTCOND*/0)
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef RTE_HLIST_INIT_VAL_DEFAULT
+/** Initialising value used when calculating hash. */
+#define RTE_HLIST_INIT_VAL_DEFAULT 0xFFFFFFFF
+#endif
+
+/* Macro to enable/disable run-time checking of function parameters */
+#if defined(RTE_LIBRTE_HASH_DEBUG)
+#define RETURN_IF_TRUE(cond, retval) do { \
+ if (cond) \
+ return retval; \
+} while (0)
+
+#define RETURN_IF_TRUE_ERRNO(cond, retval, err) do { \
+ if (cond) { \
+ rte_errno = err; \
+ return retval; \
+ } \
+ } while (0)
+#else
+#define RETURN_IF_TRUE(cond, retval)
+#define RETURN_IF_TRUE_ERRNO(cond, retval, err)
+#endif
+
+/** Maximum size of string for naming the hlist table. */
+#define RTE_HLIST_NAMESIZE 32
+
+/** The maximum number of entries in the hlist table that is supported. */
+#define RTE_HLIST_ENTRIES_MAX (1 << 24)
+
+/** The maximum number of entries in each list that is supported. */
+#define RTE_HLIST_ENTRIES_PER_BUCKET_MAX 128
+
+/** Calculate the previous mask before splitting */
+#define HLIST_CALC_PREVIOUS_BUCKET_MASK(v, s) ((((v)+1) >> (s)) - 1)
+
+/** Flag bits for the data stored */
+#define HLIST_DATA_NEW_ENTRY (0x80000000)
+#define HLIST_DATA_CUSTOM_EXTRA_DATA (0x40000000)
+#define HLIST_DATA_INLINE (0x20000000)
+#define HLIST_DATA_ALLOC_WITH_SIZE(s) \
+ (0x10000000 | ((0x0fffffff) & (s)))
+#define HLIST_DATA_IS_ALLOCATED(d) (!!((d)->flags & 0x10000000))
+#define HLIST_DATA_NOT_EXIST (0x00000000)
+
+/** Signature of key that is stored internally. */
+typedef uint32_t hash_sig_t;
+
+/** Type of function that can be used for calculating the hash value. */
+typedef uint32_t (*rte_hlist_calc_fn)(const void *key, uint32_t key_len,
+ uint32_t init_val);
+
+/** Type of function that is used to free the data from customer. */
+typedef void (*rte_hlist_free_fn)(void *p);
+
+/** Parameters used when creating hash list table. */
+struct rte_hlist_params {
+ const char *name; /**< Name of the hash table. */
+ uint32_t entries; /**< Total number of entries. */
+ uint32_t entries_per_bucket; /**< Number of entries in a bucket. */
+ /**< Length of the key to be calcuated. */
+ uint32_t key_len;
+ int socket_id; /**< Socket to allocate memory on. */
+ rte_hlist_calc_fn hash_func; /**< The hash function to calcuate. */
+ /**< The function to free the custom data. */
+ rte_hlist_free_fn free_func;
+ uint32_t init_val; /**< For initializing hash function. */
+};
+
+/** Data element structure for future use */
+struct rte_hlist_data_element {
+ /**< Union for data, pointer or inline */
+ union {
+ void *extra_data;
+ uint64_t v64;
+ uint32_t v32;
+ uint16_t v16;
+ uint8_t v8;
+ };
+ uint32_t sig; /**< Calcuated hash value. */
+ uint32_t flags; /**< Flag bits of the data store. */
+};
+
+/** Node element structure on the LIST of the link */
+struct rte_hlist_node_entry {
+ LIST_ENTRY(rte_hlist_node_entry) next; /**< Next element pointer. */
+ /**< Data element inside this noed. */
+ struct rte_hlist_data_element d;
+ char key[]; /**< Copied and stored key. */
+};
+
+/** Head of all the nodes with the same hash value */
+struct rte_hlist_head_entry {
+ LIST_HEAD(, rte_hlist_node_entry) head; /**< Head for each hash list. */
+ /**< Current items in the list. */
+ uint16_t entries_in_bucket;
+ /**< Shift number for extension */
+ uint16_t bucket_shift;
+};
+
+/** The hlist table structure. */
+struct rte_hlist_table {
+ char name[RTE_HLIST_NAMESIZE]; /**< Name of the hash. */
+ uint32_t entries; /**< Total number of entries. */
+ uint32_t entries_per_bucket; /**< Number of entries in a list. */
+ /**< Number of entries with data from customer. */
+ uint32_t custom_entries;
+ uint16_t key_len; /**< Length of the key. */
+ /**< Shift number of the whole table. */
+ uint16_t bucket_shift;
+ /**< To find which list the key is in. */
+ uint32_t bucket_mask;
+ rte_hlist_calc_fn hash_func; /**< The hash function to calcuate. */
+ /**< The function to free the custom data. */
+ rte_hlist_free_fn free_func;
+ uint32_t init_val; /**< For initializing hash function. */
+ /**< Reserved for fast shrinking of the table. */
+ char *map;
+ /**< A flat and extendible table of all lists. */
+ struct rte_hlist_head_entry *t;
+};
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Calc a hash value by key.
+ * This operation is not multi-thread safe.
+ *
+ * @param h
+ * Hlist table to look in.
+ * @param key
+ * Key to calc.
+ * @return
+ * - hash value
+ */
+__rte_experimental
+hash_sig_t
+rte_hlist_hash(const struct rte_hlist_table *h, const void *key);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Add a key to an existing hash list without data, or return the existing
+ * element associated with the key. This operation is not multi-thread safe
+ * and should only be called from one thread.
+ *
+ * @param h
+ * Hlist table to add the key to.
+ * @param key
+ * Key to add to the hlist table.
+ * @return
+ * - NULL pointer if failed to add the key, check the variable rte_errno for
+ * more error information.
+ * - A data element pointer with useful information for future use.
+ */
+__rte_experimental
+struct rte_hlist_data_element *
+rte_hlist_add_key(struct rte_hlist_table *h, const void *key);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Add a key to an existing hash list with customer data, or return the existing
+ * element associated with the key(data will be ignored). This operation is not
+ * multi-thread safe and should only be called from one thread.
+ *
+ * @param h
+ * Hlist table to add the key to.
+ * @param key
+ * Key to add to the hlist table.
+ * @param data
+ * Customer data pointer, and it is the caller's responsibility not to release
+ * it if the data area will be accessed later.
+ * @return
+ * - NULL pointer if failed to add the key, check the variable rte_errno for
+ * more error information.
+ * - A data element pointer with useful information for future use.
+ */
+__rte_experimental
+struct rte_hlist_data_element *
+rte_hlist_add_key_data(struct rte_hlist_table *h, const void *key, void *data);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Add a key to an existing hash list with customer data, or return the existing
+ * element associated with the key(data will be ignored). This operation is not
+ * multi-thread safe and should only be called from one thread.
+ *
+ * @param h
+ * Hlist table to add the key to.
+ * @param key
+ * Key to add to the hlist table.
+ * @param data
+ * Customer data pointer, and it is the caller's responsibility not to release
+ * it if the data area will be accessed later.
+ * @param len
+ * The length of the customer's data.
+ * @param cus
+ * The allocation attribute of the data. If yes, it is the customer's
+ * responsibility to hold/release the data, or else, a copy of the data will
+ * be held and the data pointer is free to release or re-use.
+ * @return
+ * - NULL pointer if failed to add the key, check the variable rte_errno for
+ * more error information.
+ * - A data element pointer with useful information for future use.
+ */
+__rte_experimental
+struct rte_hlist_data_element *
+rte_hlist_add_key_data_len(struct rte_hlist_table *h, const void *key,
+ void *data, uint16_t len, uint8_t cus);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Add a key to an existing hash list with pre-calculated hash signature and
+ * customer data, or return the existing element associated with the key(data
+ * will be ignored). This operation is not multi-thread safe and should only
+ * be called from one thread.
+ *
+ * @param h
+ * Hlist table to add the key to.
+ * @param key
+ * Key to add to the hlist table.
+ * @param sig
+ * Precomputed hash value for 'key'.
+ * @param data
+ * Customer data pointer, and it is the caller's responsibility not to release
+ * it if the data area will be accessed later.
+ * @return
+ * - NULL pointer if failed to add the key, check the variable rte_errno for
+ * more error information.
+ * - A data element pointer with useful information for future use.
+ */
+__rte_experimental
+struct rte_hlist_data_element *
+rte_hlist_add_key_with_hash_data(struct rte_hlist_table *h, const void *key,
+ hash_sig_t sig, void *data);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Add a key to an existing hash list with pre-calculated hash signature and
+ * customer data, or return the existing element associated with the key(data
+ * will be ignored). This operation is not multi-thread safe and should only
+ * be called from one thread.
+ *
+ * @param h
+ * Hlist table to add the key to.
+ * @param key
+ * Key to add to the hlist table.
+ * @param sig
+ * Precomputed hash value for 'key'.
+ * @param data
+ * Customer data pointer.
+ * @param len
+ * The length of the customer's data.
+ * @param cus
+ * The allocation attribute of the data. If yes, it is the customer's
+ * responsibility to hold/release the data, or else, a copy of the data will
+ * be held and the data pointer is free to release or re-use.
+ * @return
+ * - NULL pointer if failed to add the key, check the variable rte_errno for
+ * more error information.
+ * - A data element pointer with useful information for future use.
+ */
+__rte_experimental
+struct rte_hlist_data_element *
+rte_hlist_add_key_with_hash_data_len(struct rte_hlist_table *h, const void *key,
+ hash_sig_t sig, void *data, uint16_t len, uint8_t cus);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Remove a key from an existing hlist table. This operation is not multi-thread
+ * safe and should only be called from one thread.
+ *
+ * @param h
+ * Hlist table to remove the key from.
+ * @param key
+ * Key to remove from the hlist table.
+ * @return
+ * - -EINVAL if the parameters are invalid.
+ * - -ENOMEM if failed to extend the lists head.
+ * - -ENOENT if the key is not found.
+ * - -EBUSY if there is custom data but without custom free fuction.
+ * - zero for success.
+ */
+__rte_experimental
+int
+rte_hlist_del_key(struct rte_hlist_table *h, const void *key);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Remove a key from an existing hlist table, and return the data pointer to the
+ * customer if any but without trying to free it. This operation is not
+ * multi-thread safe and should only be called from one thread.
+ *
+ * @param h
+ * Hlist table to remove the key from.
+ * @param key
+ * Key to remove from the hlist table.
+ * @param data
+ * Output containing a pointer to the data.
+ * @return
+ * - -EINVAL if the parameters are invalid.
+ * - -ENOMEM if failed to extend the lists head.
+ * - -ENOENT if the key is not found.
+ * - zero for success.
+ */
+__rte_experimental
+int
+rte_hlist_del_key_return_data(struct rte_hlist_table *h, const void *key,
+ void **data);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Remove an entry from an existing hlist table, and return the data pointer to
+ * the customer if any but without trying to free it. User should ensure the
+ * integrity of the entry. This operation is not multi-thread safe and should
+ * only be called from one thread.
+ *
+ * @param h
+ * Hlist table to remove the key from.
+ * @param de
+ * Data element to be removed from the hlist table.
+ * @param data
+ * Output containing a pointer to the data.
+ * @return
+ * - -EINVAL if the parameters are invalid.
+ * - zero for success.
+ */
+__rte_experimental
+int
+rte_hlist_del_entry_fast_return_data(struct rte_hlist_table *h,
+ struct rte_hlist_data_element *de, void **data);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Find a key in the hlist table.
+ * This operation is not multi-thread safe.
+ *
+ * @param h
+ * Hlist table to look in.
+ * @param key
+ * Key to find.
+ * @return
+ * - NULL if failed to search the key in the hlist. Check the rte_errno for
+ * more error information.
+ * -EINVAL if the parameters are invalid.
+ * -ENOMEM if failed to extend the lists head.
+ * -ENOENT if the key is not found.
+ * - A data element pointer with useful information for future use.
+ */
+__rte_experimental
+struct rte_hlist_data_element *
+rte_hlist_lookup(struct rte_hlist_table *h, const void *key);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Find a key in the hlist table.
+ * This operation is not multi-thread safe.
+ *
+ * @param h
+ * Hlist table to look in.
+ * @param key
+ * Key to find.
+ * @param sig
+ * Precomputed hash value for 'key'.
+ * @return
+ * - NULL if failed to search the key in the hlist. Check the rte_errno for
+ * more error information.
+ * -EINVAL if the parameters are invalid.
+ * -ENOMEM if failed to extend the lists head.
+ * -ENOENT if the key is not found.
+ * - A data element pointer with useful information for future use.
+ */
+__rte_experimental
+struct rte_hlist_data_element *
+rte_hlist_lookup_with_hash(struct rte_hlist_table *h, const void *key,
+ hash_sig_t sig);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Check if the data entry is a new one.
+ * This operation is not multi-thread safe.
+ *
+ * @param d
+ * Pointer to the data entry.
+ * @return
+ * No is 0 and Yes is 1.
+ */
+__rte_experimental
+static inline uint32_t
+rte_hlist_entry_is_new(struct rte_hlist_data_element *d)
+{
+ return !!(d->flags & HLIST_DATA_NEW_ENTRY);
+}
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Append customer data to the data element.
+ * This operation is not multi-thread safe.
+ *
+ * @param h
+ * Hlist table to look in.
+ * @param d
+ * Pointer to the data entry.
+ * @param data
+ * Data address to be append.
+ * @return
+ * - -EINVAL if the parameters are invalid.
+ * - -EEXIST if there is alreay some data.
+ * - 0 if succeed.
+ */
+__rte_experimental
+static inline int
+rte_hlist_entry_append_custom_data(struct rte_hlist_table *h,
+ struct rte_hlist_data_element *d, void *data)
+{
+ if ((h == NULL) || (d == NULL) || (data == NULL))
+ return -EINVAL;
+
+ if ((d->flags & (~HLIST_DATA_NEW_ENTRY)) != HLIST_DATA_NOT_EXIST)
+ return -EEXIST;
+
+ d->extra_data = data;
+ d->flags |= HLIST_DATA_CUSTOM_EXTRA_DATA;
+ h->custom_entries++;
+ return 0;
+}
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Create a new hlist table. This operation is not multi-thread safe.
+ *
+ * @param params
+ * Parameters used in creation of hlist table.
+ *
+ * @return
+ * Pointer to hlist table structure that is used in future hlist table
+ * operations, or NULL on error with error code set in rte_errno.
+ */
+__rte_experimental
+struct rte_hlist_table *
+rte_hlist_create(const struct rte_hlist_params *params);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Free all memory used by a hlist table. Note, this will force to try to
+ * release all the customer's data before it cleans the memories of the table.
+ *
+ * @param h
+ * Hlist table to deallocate.
+ * @return
+ * - 0 if succeed.
+ * - -EINVAL if the parameters are invalid.
+ * - -EEXIST if the hlist doesn't exist.
+ */
+__rte_experimental
+int rte_hlist_free(struct rte_hlist_table *h);
+
+/**
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice
+ *
+ * Free all entries in the hash list table, and the customer's data could be
+ * handled by the callback function (optional).
+ *
+ * @param h
+ * Hlist table to deallocate.
+ * @param fn
+ * Callback function for the customer data handling.
+ * @return
+ * - 0 if succeed.
+ * - -EINVAL if the parameters are invalid.
+ * - -EBUSY if the hlist doesn't exist.
+ */
+__rte_experimental
+int rte_hlist_clear_all_entries_with_cb(struct rte_hlist_table *h,
+ rte_hlist_free_fn fn);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_HLIST_H_ */
--
1.8.3.1
next prev parent reply other threads:[~2019-08-28 8:18 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-08-28 6:51 [dpdk-dev] [RFC] hash: introduce resizable hash list Bing Zhao
2019-08-28 6:51 ` Bing Zhao [this message]
2019-08-28 11:50 ` [dpdk-dev] [RFC] rte_hash: introduce hash list into hash lib Stephen Hemminger
2019-08-28 11:51 ` Stephen Hemminger
2019-08-28 11:53 ` Stephen Hemminger
2019-09-05 19:25 ` [dpdk-dev] [RFC] hash: introduce resizable hash list Wang, Yipeng1
2019-09-12 5:41 ` Bing Zhao
2019-09-21 1:16 ` Wang, Yipeng1
2023-06-12 16:44 ` Stephen Hemminger
2023-06-13 6:34 ` Bing Zhao
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1566975109-318949-2-git-send-email-bingz@mellanox.com \
--to=bingz@mellanox.com \
--cc=bruce.richardson@intel.com \
--cc=dev@dpdk.org \
--cc=pablo.de.lara.guarch@intel.com \
--cc=sameh.gobriel@intel.com \
--cc=yipeng1.wang@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).