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

Reply via email to