RE: [RFC v4 2/3] lib: add fastmem library

Morten Brørup <[email protected]>
Newsgroups org.dpdk.dev
Message-ID <[email protected]>
Hi Mattias,

It would be great to get fastmem into DPDK 26.11.

I have reviewed the patch for features and usage. Not for correctness.

Here are my comments, in somewhat random order.

-

Export symbols for 26.11, not 24.11.

-

Generally, consider setting rte_errno and returning -1, instead of returning -ESOMETHING.
Then, e.g. in bin_alloc_one(), if slab_acquire() returns NULL, grow_socket() (called by slab_acquire()) has already set rte_errno.

-

+/*
+ * Slab header at offset 0 of each 2 MiB slab. Either free (linked
+ * via next_free) or assigned to a bin (linked via list).
+ */
+struct fastmem_slab {
+	struct fastmem_bin *bin;
+	void *free_head;
+	uint32_t free_count;
+	uint32_t n_slots;
+	struct fastmem_slab *next_free;
+	TAILQ_ENTRY(fastmem_slab) list;
+	rte_iova_t iova_base;
+} __rte_aligned(FASTMEM_SLAB_HEADER_SIZE);

I wonder if rte_fastmem_virt2iova() is going to be a hot function?
Then it should be inline, and the iova_base field should be first in the fastmem_slab for easier access (and possibly faster on architectures without "load with offset" instructions).

The rte_fastmem_virt2iova() function can be inline without exposing fastmem internals by using opaque pointer magic.
The opaque pointer magic should be protected by a bunch of static_assert()'s in rte_fastmem.c, mentioning that rte_fastmem_virt2iova() depends on these.
Something like: 

static inline rte_iova_t
rte_fastmem_virt2iova(const void *ptr)
{
//	struct fastmem_slab *slab;
	void *slab;

//	slab = slab_of((void *)(uintptr_t)ptr);
//	slab = (uintptr_t)ptr & ~(uintptr_t)FASTMEM_SLAB_MASK;
	slab = ((uintptr_t)ptr & ~(uintptr_t)((1 << 21) - 1));
	// rte_fastmem.c: static_assert(FASTMEM_SLAB_MASK == (1 << 21) - 1),
		"Mismatch in rte_fastmem_virt2iova()");

//	return slab->iova_base + ((uintptr_t)ptr - (uintptr_t)slab);
	return *(rte_iova_t *)slab + ((uintptr_t)ptr - (uintptr_t)slab);
	// rte_fastmem.c: static_assert(offsetof(struct fastmem_slab, iova_base) == 0,
		"Mismatch in rte_fastmem_virt2iova()");
}

-

Generally, statistics counting should be build-time configurable enable/disable for performance.
It's acceptable to keep the variables in the structs, and just not update them.
Consider (with statistics disabled) removing the statistics variables from the fastmem_cache structure, so the objects at the bottom of the stack are in the same cache line as the count/capacity fields. (If it makes any practical difference.)

-

+struct fastmem_cache {
+	uint32_t count;
+	uint32_t capacity;
+	uint32_t target;
+	uint64_t alloc_cache_hits;
+	uint64_t alloc_cache_misses;
+	uint64_t alloc_nomem;
+	uint64_t free_cache_hits;
+	uint64_t free_cache_misses;
+	void *objs[];
+} __rte_cache_aligned;

"target" is a shadow of "capacity / 2".
It is rarely used without also accessing "capacity".
Consider dropping it, and just using capacity / 2.
It might even improve performance in some locations, where "capacity" is already loaded into a CPU register or will be used shortly thereafter. (My performance comment is pure speculation, not measured.) (For reference, the mempool lib uses cache->size / 2.)

-

Initialization should be done at the proper DPDK startup stage, not lazily at all fastmem functions.
In the fastmem functions, calling fastmem_assure() should be replaced by a simple RTE_ASSERT(fastmem != NULL).

I was wondering how do other libs handle the situation where a secondary process depends on init performed by the primary function?
It looks like they do something similiar to fastmem_assure(). This sucks!
That's an important feature gap in DPDK, which I already registered as a bug on the wishlist: https://bugs.dpdk.org/show_bug.cgi?id=1913

Until we get that wish fixed, we should make eal depend on fastmem, and call the fastmem init function from inside rte_eal_init().

-

Do you foresee any other flags than RTE_FASTMEM_F_ZERO?
I'd prefer not passing a flags parameter to the fastmem public alloc APIs, and instead expose two variants of the alloc APIs.
It's OK to pass around the zero flag (or a boolean) internally; it will be optimized away because it is known at build time from the two alloc API variants.

-

A fastmem handle "rte_fastmem_handle_t" is not an object handle, so the name could be misleading.
It's rather a sort of accelerator or helper for faster alloc/free.

Also, it doesn't support SOCKET_ID_ANY (-1).
Would it be beneficial to support SOCKET_ID_ANY, so the socket_id part gets resolved when using the fastmem handle?
Or would that defeat the performance gain of fastmem handles?
And are there relevant use cases for fastmem handle supporting SOCKET_ID_ANY, or am I pointlessly feature creeping?

-

+#define FASTMEM_MIN_SIZE ((size_t)1 << FASTMEM_MIN_CLASS_LOG2)
+#define FASTMEM_MAX_ALLOC_SIZE ((size_t)1 << FASTMEM_MAX_CLASS_LOG2)

Min and Max names should be similar.

-

rte_fastmem_cache_flush():
The function name could indicate that it operates on the current lcore only.
Makes code using the function easier to read, for reviewers unfamiliar with the fastmem lib.

-

In grow_socket():
+	if (socket->n_memzones == FASTMEM_MAX_MEMZONES_PER_SOCKET) {
+		FASTMEM_LOG(ERR,
+			"reached per-socket memzone cap (%u) on socket %d",

cap -> limit

-

In bin_push_locked():

+		struct fastmem_slab *slab = (struct fastmem_slab *)
+			((uintptr_t)obj & ~(uintptr_t)FASTMEM_SLAB_MASK);

-> slab_of()

-Morten
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.