[2/4] net/mlx5: add indexed pool local cache

Message ID 20210527093403.1153127-3-suanmingm@nvidia.com (mailing list archive)
State Superseded, archived
Delegated to: Raslan Darawsheh
Headers
Series net/mlx5: add indexed pool local cache |

Checks

Context Check Description
ci/checkpatch success coding style OK

Commit Message

Suanming Mou May 27, 2021, 9:34 a.m. UTC
  For object which wants efficient index allocate and free, local
cache will be very helpful.

Two level cache is added, one as local and another as global.
The global cache is able to save all the allocated index. That
means all the allocated index will not be freed. Once the local
cache is full, the extra index will be flushed to the global
cache. Once local cache is empty, first try to fetch more index
from global, if global is still empty, allocate new trunk and
more index.

This commit adds a new local ring cache mechanism for indexed
pool.

Signed-off-by: Suanming Mou <suanmingm@nvidia.com>
---
 drivers/net/mlx5/mlx5_utils.c | 253 +++++++++++++++++++++++++++++++++-
 drivers/net/mlx5/mlx5_utils.h |  55 +++++++-
 2 files changed, 306 insertions(+), 2 deletions(-)
  

Patch

diff --git a/drivers/net/mlx5/mlx5_utils.c b/drivers/net/mlx5/mlx5_utils.c
index bf2b2ebc72..900de2a831 100644
--- a/drivers/net/mlx5/mlx5_utils.c
+++ b/drivers/net/mlx5/mlx5_utils.c
@@ -273,6 +273,22 @@  mlx5_ipool_create(struct mlx5_indexed_pool_config *cfg)
 	if (!pool->cfg.max_idx)
 		pool->cfg.max_idx =
 			mlx5_trunk_idx_offset_get(pool, TRUNK_MAX_IDX + 1);
+	if (cfg->per_core_cache) {
+		char cache_name[64] = { 0 };
+
+		for (i = 0; i < MLX5_IPOOL_MAX_CORES; i++) {
+			snprintf(cache_name, RTE_DIM(cache_name),
+				 "Ipool_cache-%p-%u", (void *)pool, i);
+			pool->l_idx_c[i] = rte_ring_create(cache_name,
+				cfg->per_core_cache, SOCKET_ID_ANY,
+				RING_F_SC_DEQ | RING_F_SP_ENQ);
+			if (!pool->l_idx_c[i]) {
+				printf("Ipool allocate ring failed:%d\n", i);
+				mlx5_free(pool);
+				return NULL;
+			}
+		}
+	}
 	return pool;
 }
 
@@ -355,6 +371,232 @@  mlx5_ipool_grow(struct mlx5_indexed_pool *pool)
 	return 0;
 }
 
+static struct mlx5_indexed_trunk *
+mlx5_ipool_grow_cache(struct mlx5_indexed_pool *pool)
+{
+	struct mlx5_indexed_trunk *trunk;
+	struct mlx5_indexed_cache *p, *pi;
+	size_t trunk_size = 0;
+	size_t data_size;
+	uint32_t cur_max_idx, trunk_idx;
+	int n_grow;
+	int cidx = 0;
+	char cache_name[64] = { 0 };
+
+	cur_max_idx = mlx5_trunk_idx_offset_get(pool, pool->n_trunk_valid);
+	if (pool->n_trunk_valid == TRUNK_MAX_IDX ||
+	    cur_max_idx >= pool->cfg.max_idx)
+		return NULL;
+	cidx = rte_lcore_index(rte_lcore_id());
+	if (cidx == -1 || cidx > (MLX5_IPOOL_MAX_CORES - 1))
+		cidx = 0;
+	trunk_idx = __atomic_fetch_add(&pool->n_trunk_valid, 1,
+				__ATOMIC_ACQUIRE);
+	/* Have enough space in trunk array. Allocate the trunk directly. */
+	if (trunk_idx < __atomic_load_n(&pool->n_trunk, __ATOMIC_ACQUIRE))
+		goto allocate_trunk;
+	mlx5_ipool_lock(pool);
+	/* Double check if trunks array has been resized. */
+	if (trunk_idx < __atomic_load_n(&pool->n_trunk, __ATOMIC_ACQUIRE)) {
+		mlx5_ipool_unlock(pool);
+		goto allocate_trunk;
+	}
+	n_grow = trunk_idx ? pool->n_trunk :
+			     RTE_CACHE_LINE_SIZE / sizeof(void *);
+	cur_max_idx = mlx5_trunk_idx_offset_get(pool, pool->n_trunk + n_grow);
+	/* Resize the trunk array. */
+	p = pool->cfg.malloc(MLX5_MEM_ZERO, ((trunk_idx + n_grow) *
+			     sizeof(struct mlx5_indexed_trunk *)) + sizeof(*p),
+			     RTE_CACHE_LINE_SIZE, rte_socket_id());
+	if (!p) {
+		mlx5_ipool_unlock(pool);
+		return NULL;
+	}
+	p->trunks = (struct mlx5_indexed_trunk **)(p + 1);
+	if (pool->trunks_g)
+		memcpy(p->trunks, pool->trunks_g->trunks, trunk_idx *
+		       sizeof(struct mlx5_indexed_trunk *));
+	memset(RTE_PTR_ADD(p->trunks, trunk_idx * sizeof(void *)), 0,
+	       n_grow * sizeof(void *));
+	/* Resize the global index cache ring. */
+	pi = pool->cfg.malloc(MLX5_MEM_ZERO, sizeof(*pi), 0, rte_socket_id());
+	if (!pi) {
+		mlx5_free(p);
+		mlx5_ipool_unlock(pool);
+		return NULL;
+	}
+	snprintf(cache_name, RTE_DIM(cache_name), "Idxc-%p-%u",
+		 (void *)pool, trunk_idx);
+	pi->ring = rte_ring_create(cache_name, rte_align32pow2(cur_max_idx),
+		SOCKET_ID_ANY, RING_F_SC_DEQ | RING_F_SP_ENQ);
+	if (!pi->ring) {
+		mlx5_free(p);
+		mlx5_free(pi);
+		mlx5_ipool_unlock(pool);
+		return NULL;
+	}
+	p->ref_cnt = 1;
+	pool->trunks_g = p;
+	pi->ref_cnt = 1;
+	pool->idx_g = pi;
+	/* Check if trunks array is not used any more. */
+	if (pool->trunks_c[cidx] && (!(--pool->trunks_c[cidx]->ref_cnt)))
+		mlx5_free(pool->trunks_c[cidx]);
+	/* Check if index cache is not used any more. */
+	if (pool->idx_c[cidx] &&
+	    (!(--pool->idx_c[cidx]->ref_cnt))) {
+		rte_ring_free(pool->idx_c[cidx]->ring);
+		mlx5_free(pool->idx_c[cidx]);
+	}
+	pool->trunks_c[cidx] = p;
+	pool->idx_c[cidx] = pi;
+	__atomic_fetch_add(&pool->n_trunk, n_grow, __ATOMIC_ACQUIRE);
+	mlx5_ipool_unlock(pool);
+allocate_trunk:
+	/* Initialize the new trunk. */
+	trunk_size = sizeof(*trunk);
+	data_size = mlx5_trunk_size_get(pool, trunk_idx);
+	trunk_size += RTE_CACHE_LINE_ROUNDUP(data_size * pool->cfg.size);
+	trunk = pool->cfg.malloc(0, trunk_size,
+				 RTE_CACHE_LINE_SIZE, rte_socket_id());
+	if (!trunk)
+		return NULL;
+	pool->trunks_g->trunks[trunk_idx] = trunk;
+	trunk->idx = trunk_idx;
+	trunk->free = data_size;
+#ifdef POOL_DEBUG
+	pool->trunk_new++;
+#endif
+	return trunk;
+}
+
+static void *
+mlx5_ipool_get_cache(struct mlx5_indexed_pool *pool, uint32_t idx)
+{
+	struct mlx5_indexed_trunk *trunk;
+	uint32_t trunk_idx;
+	uint32_t entry_idx;
+	int cidx = 0;
+
+	if (!idx)
+		return NULL;
+	cidx = rte_lcore_index(rte_lcore_id());
+	if (cidx == -1)
+		cidx = 0;
+	if (pool->trunks_c[cidx] != pool->trunks_g) {
+		mlx5_ipool_lock(pool);
+		/* Rlease the old ring if we are the last thread cache it. */
+		if (pool->trunks_c[cidx] &&
+		    !(--pool->trunks_c[cidx]->ref_cnt))
+			mlx5_free(pool->trunks_c[cidx]);
+		pool->trunks_c[cidx] = pool->trunks_g;
+		pool->trunks_c[cidx]->ref_cnt++;
+		mlx5_ipool_unlock(pool);
+	}
+	idx -= 1;
+	trunk_idx = mlx5_trunk_idx_get(pool, idx);
+	trunk = pool->trunks_c[cidx]->trunks[trunk_idx];
+	if (!trunk)
+		return NULL;
+	entry_idx = idx - mlx5_trunk_idx_offset_get(pool, trunk->idx);
+	return &trunk->data[entry_idx * pool->cfg.size];
+}
+
+static void *
+mlx5_ipool_malloc_cache(struct mlx5_indexed_pool *pool, uint32_t *idx)
+{
+	struct mlx5_indexed_trunk *trunk;
+	uint32_t i, ts_idx, fetch_size;
+	int cidx = 0;
+	union mlx5_indexed_qd qd;
+
+	cidx = rte_lcore_index(rte_lcore_id());
+	if (cidx == -1)
+		cidx = 0;
+	/* Try local cache firstly. */
+	if (!rte_ring_dequeue(pool->l_idx_c[cidx], &qd.ptr)) {
+		*idx = qd.idx;
+		return mlx5_ipool_get_cache(pool, qd.idx);
+	}
+	if (pool->idx_g) {
+		/*
+		 * Try fetch from the global cache. Check if global cache ring
+		 * updated first.
+		 */
+		if (pool->idx_c[cidx] != pool->idx_g) {
+			mlx5_ipool_lock(pool);
+			/* Rlease the old ring as last user. */
+			if (pool->idx_c[cidx] &&
+			    !(--pool->idx_c[cidx]->ref_cnt)) {
+				rte_ring_free(pool->idx_c[cidx]->ring);
+				pool->cfg.free(pool->idx_c[cidx]);
+			}
+			pool->idx_c[cidx] = pool->idx_g;
+			pool->idx_c[cidx]->ref_cnt++;
+			mlx5_ipool_unlock(pool);
+		}
+		fetch_size = pool->cfg.trunk_size;
+		while (!rte_ring_dequeue(pool->idx_g->ring, &qd.ptr)) {
+			if (unlikely(!(--fetch_size))) {
+				*idx = qd.idx;
+				return mlx5_ipool_get_cache(pool, qd.idx);
+			}
+			rte_ring_enqueue(pool->l_idx_c[cidx], qd.ptr);
+		}
+	}
+	/* Not enough idx in global cache. Keep fetching from new trunk. */
+	trunk = mlx5_ipool_grow_cache(pool);
+	if (!trunk)
+		return NULL;
+	/* Get trunk start index. */
+	ts_idx = mlx5_trunk_idx_offset_get(pool, trunk->idx);
+	/* Enqueue trunk_size - 1 to local cache ring. */
+	for (i = 0; i < trunk->free - 1; i++) {
+		qd.idx = ts_idx + i + 1;
+		rte_ring_enqueue(pool->l_idx_c[cidx], qd.ptr);
+	}
+	/* Return trunk's final entry. */
+	*idx = ts_idx + i + 1;
+	return &trunk->data[i * pool->cfg.size];
+}
+
+static void
+mlx5_ipool_free_cache(struct mlx5_indexed_pool *pool, uint32_t idx)
+{
+	int cidx;
+	uint32_t i, reclaim_num = 0;
+	union mlx5_indexed_qd qd;
+
+	if (!idx)
+		return;
+	cidx = rte_lcore_index(rte_lcore_id());
+	if (cidx == -1)
+		cidx = 0;
+	qd.idx = idx;
+	/* Try to enqueue to local index cache. */
+	if (!rte_ring_enqueue(pool->l_idx_c[cidx], qd.ptr))
+		return;
+	/* Update the global index cache ring if needed. */
+	if (pool->idx_c[cidx] != pool->idx_g) {
+		mlx5_ipool_lock(pool);
+		/* Rlease the old ring if we are the last thread cache it. */
+		if (!(--pool->idx_c[cidx]->ref_cnt))
+			rte_ring_free(pool->idx_c[cidx]->ring);
+		pool->idx_c[cidx] = pool->idx_g;
+		pool->idx_c[cidx]->ref_cnt++;
+		mlx5_ipool_unlock(pool);
+	}
+	reclaim_num = pool->cfg.per_core_cache >> 4;
+	/* Local index cache full, try with global index cache. */
+	rte_ring_enqueue(pool->idx_c[cidx]->ring, qd.ptr);
+	/* Dequeue the index from local cache to global. */
+	for (i = 0; i < reclaim_num; i++) {
+		/* No need to check the return value. */
+		rte_ring_dequeue(pool->l_idx_c[cidx], &qd.ptr);
+		rte_ring_enqueue(pool->idx_c[cidx]->ring, qd.ptr);
+	}
+}
+
 void *
 mlx5_ipool_malloc(struct mlx5_indexed_pool *pool, uint32_t *idx)
 {
@@ -363,6 +605,8 @@  mlx5_ipool_malloc(struct mlx5_indexed_pool *pool, uint32_t *idx)
 	uint32_t iidx = 0;
 	void *p;
 
+	if (pool->cfg.per_core_cache)
+		return mlx5_ipool_malloc_cache(pool, idx);
 	mlx5_ipool_lock(pool);
 	if (pool->free_list == TRUNK_INVALID) {
 		/* If no available trunks, grow new. */
@@ -432,6 +676,8 @@  mlx5_ipool_free(struct mlx5_indexed_pool *pool, uint32_t idx)
 
 	if (!idx)
 		return;
+	if (pool->cfg.per_core_cache)
+		return mlx5_ipool_free_cache(pool, idx);
 	idx -= 1;
 	mlx5_ipool_lock(pool);
 	trunk_idx = mlx5_trunk_idx_get(pool, idx);
@@ -497,6 +743,8 @@  mlx5_ipool_get(struct mlx5_indexed_pool *pool, uint32_t idx)
 
 	if (!idx)
 		return NULL;
+	if (pool->cfg.per_core_cache)
+		return mlx5_ipool_get_cache(pool, idx);
 	idx -= 1;
 	mlx5_ipool_lock(pool);
 	trunk_idx = mlx5_trunk_idx_get(pool, idx);
@@ -524,7 +772,10 @@  mlx5_ipool_destroy(struct mlx5_indexed_pool *pool)
 
 	MLX5_ASSERT(pool);
 	mlx5_ipool_lock(pool);
-	trunks = pool->trunks;
+	if (pool->cfg.per_core_cache)
+		trunks = pool->trunks_g->trunks;
+	else
+		trunks = pool->trunks;
 	for (i = 0; i < pool->n_trunk; i++) {
 		if (trunks[i])
 			pool->cfg.free(trunks[i]);
diff --git a/drivers/net/mlx5/mlx5_utils.h b/drivers/net/mlx5/mlx5_utils.h
index 15870e14c2..4fe82d4a5f 100644
--- a/drivers/net/mlx5/mlx5_utils.h
+++ b/drivers/net/mlx5/mlx5_utils.h
@@ -181,10 +181,17 @@  typedef int32_t (*mlx5_l3t_alloc_callback_fn)(void *ctx,
 #define TRUNK_MAX_IDX ((1 << TRUNK_IDX_BITS) - 1)
 #define TRUNK_INVALID TRUNK_MAX_IDX
 #define MLX5_IPOOL_DEFAULT_TRUNK_SIZE (1 << (28 - TRUNK_IDX_BITS))
+#define MLX5_IPOOL_MAX_CORES (1 << 4)
 #ifdef RTE_LIBRTE_MLX5_DEBUG
 #define POOL_DEBUG 1
 #endif
 
+union mlx5_indexed_qd {
+	RTE_STD_C11
+	void *ptr;
+	uint32_t idx;
+};
+
 struct mlx5_indexed_pool_config {
 	uint32_t size; /* Pool entry size. */
 	uint32_t trunk_size:22;
@@ -209,6 +216,11 @@  struct mlx5_indexed_pool_config {
 	/* Lock is needed for multiple thread usage. */
 	uint32_t release_mem_en:1; /* Rlease trunk when it is free. */
 	uint32_t max_idx; /* The maximum index can be allocated. */
+	uint32_t per_core_cache;
+	/*
+	 * Cache entry number per core for performance. Should not be
+	 * set with release_mem_en.
+	 */
 	const char *type; /* Memory allocate type name. */
 	void *(*malloc)(uint32_t flags, size_t size, unsigned int align,
 			int socket);
@@ -217,6 +229,7 @@  struct mlx5_indexed_pool_config {
 };
 
 struct mlx5_indexed_trunk {
+	rte_spinlock_t lock; /* Trunk lock for multiple thread usage. */
 	uint32_t idx; /* Trunk id. */
 	uint32_t prev; /* Previous free trunk in free list. */
 	uint32_t next; /* Next free trunk in free list. */
@@ -225,13 +238,29 @@  struct mlx5_indexed_trunk {
 	uint8_t data[] __rte_cache_aligned; /* Entry data start. */
 };
 
+struct mlx5_indexed_cache {
+	union {
+		struct rte_ring *ring;
+		struct mlx5_indexed_trunk **trunks;
+	};
+	uint32_t ref_cnt;
+	uint32_t res;
+};
+
 struct mlx5_indexed_pool {
 	struct mlx5_indexed_pool_config cfg; /* Indexed pool configuration. */
 	rte_spinlock_t lock; /* Pool lock for multiple thread usage. */
 	uint32_t n_trunk_valid; /* Trunks allocated. */
 	uint32_t n_trunk; /* Trunk pointer array size. */
 	/* Dim of trunk pointer array. */
-	struct mlx5_indexed_trunk **trunks;
+	union {
+		struct mlx5_indexed_trunk **trunks;
+		struct mlx5_indexed_cache *trunks_g;
+	};
+	struct mlx5_indexed_cache *trunks_c[MLX5_IPOOL_MAX_CORES];
+	struct mlx5_indexed_cache *idx_g;
+	struct mlx5_indexed_cache *idx_c[MLX5_IPOOL_MAX_CORES];
+	struct rte_ring *l_idx_c[MLX5_IPOOL_MAX_CORES];
 	uint32_t free_list; /* Index to first free trunk. */
 #ifdef POOL_DEBUG
 	uint32_t n_entry;
@@ -542,6 +571,30 @@  int mlx5_ipool_destroy(struct mlx5_indexed_pool *pool);
  */
 void mlx5_ipool_dump(struct mlx5_indexed_pool *pool);
 
+/**
+ * This function flushes all the cache index back to pool trunk.
+ *
+ * @param pool
+ *   Pointer to the index memory pool handler.
+ *
+ */
+
+void mlx5_ipool_flush_cache(struct mlx5_indexed_pool *pool);
+
+/**
+ * This function gets the available entry from pos.
+ *
+ * @param pool
+ *   Pointer to the index memory pool handler.
+ * @param pos
+ *   Pointer to the index position start from.
+ *
+ * @return
+ *  - Pointer to the next available entry.
+ *
+ */
+void *mlx5_ipool_get_next(struct mlx5_indexed_pool *pool, uint32_t *pos);
+
 /**
  * This function allocates new empty Three-level table.
  *