All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Morten Brørup" <mb@smartsharesystems.com>
To: "Stephen Hemminger" <stephen@networkplumber.org>
Cc: <dev@dpdk.org>
Subject: RE: [RFC PATCH v6] pile stack and mempool driver
Date: Sun, 2 Aug 2026 18:45:59 +0200	[thread overview]
Message-ID: <98CBD80474FA8B44BF855DF32C47DC35F659B2@smartserver.smartshare.dk> (raw)
In-Reply-To: <20260802082235.3aef3964@phoenix.local>

Thank you, Stephen.

Very useful review feedback, especially the first point!

I tried a few different algorithms for handling the mix of solo and bulk objects before arriving at this one.
But as the AI review revealed, it still needs some work.
Will follow up with a v7 patch.


Venlig hilsen / Kind regards,
-Morten Brørup


> -----Original Message-----
> From: Stephen Hemminger [mailto:stephen@networkplumber.org]
> Sent: Sunday, 2 August 2026 17.23
> 
> On Sun,  2 Aug 2026 09:59:54 +0000
> Morten Brørup <mb@smartsharesystems.com> wrote:
> 
> > Early submission of:
> > - some mempool optimizations,
> > - a new mempool "pile" driver, and
> > - its underlying "pile" stack implementation.
> >
> > For community feedback and CI test.
> >
> > Needless to say, this must be separated into a series of patches.
> > For now, I'm submitting a snapshot of work in progress.
> >
> > Some performance numbers from mempool_perf_autotest_2cores, all
> > with cache=1024 cores=2 n_keep=32768:
> >
> > start performance test (using ring_mp_mc, with cache)
> > n_get_bulk= 64 n_put_bulk= 64 constant_n=0 rate_persec= 753985338
> > n_get_bulk=256 n_put_bulk=256 constant_n=0 rate_persec= 755805913
> >
> > start performance test for lf_stack (with cache)
> > n_get_bulk= 64 n_put_bulk= 64 constant_n=0 rate_persec=  29132352
> > n_get_bulk=256 n_put_bulk=256 constant_n=0 rate_persec=  29276708
> >
> > start performance test for pile (with cache)
> > n_get_bulk= 64 n_put_bulk= 64 constant_n=0 rate_persec= 560159479
> > n_get_bulk=256 n_put_bulk=256 constant_n=0 rate_persec= 557910933
> >
> > Hat tip to Bruce for bringing attention to the ring not being the
> > optimal mempool driver.
> >
> > Signed-off-by: Morten Brørup <mb@smartsharesystems.com>
> > ---
> 
> Since relatively complex, did AI review with more advanced model.
> 
> Review of [RFC PATCH v6] pile stack and mempool driver
> 
> Errors
> 
> 1. lib/stack/rte_stack_pile.h, __rte_stack_pile_pop()
> 
> The bulk retry loop breaks the invariant that the fragmentation path
> depends on (n_solo < RTE_STACK_PILE_BULK_SIZE):
> 
> 	n_solo += RTE_STACK_PILE_BULK_SIZE;
> 	n_bulk--;
> 	if (n_bulk > 0)
> 		goto bulk;
> 	else
> 		goto solo;
> 
> Each retry adds a whole bulk worth of objects to n_solo, so control can
> reach the "solo:" label with n_solo >= RTE_STACK_PILE_BULK_SIZE (up to
> n).
> If the solo pop then fails and the fragmentation path is taken, four
> things go wrong:
> 
>  - __rte_assume(n_solo < RTE_STACK_PILE_BULK_SIZE) is false, which is
>    undefined behavior.
> 
>  - the copy loop
> 
> 	for (i = 0; i < n_solo; i++)
> 		obj_table[n_bulk * RTE_STACK_PILE_BULK_SIZE + i] =
> obj_frag[i];
> 
>    reads past the end of obj_frag[RTE_STACK_PILE_BULK_SIZE] whenever
>    n_solo > RTE_STACK_PILE_BULK_SIZE.
> 
>  - RTE_STACK_PILE_BULK_SIZE - n_solo underflows for n_solo > BULK_SIZE,
>    so free_solo is asked for ~4 billion elements.
> 
>  - for n_solo == RTE_STACK_PILE_BULK_SIZE exactly, the request becomes
>    a zero-element pop:
> 
> 	solo_first = __rte_stack_lf_pop_elems(&pile->free_solo, 0, NULL,
> &solo_last);
> 
>    __rte_stack_lf_pop_elems() with num == 0 never enters the traversal
>    loop, so it leaves *last untouched and returns old_head.top, which
> is
>    non-NULL whenever free_solo is not empty. solo_last is therefore
> still
>    NULL when
> 
> 	__rte_stack_lf_push_elems(&pile->solo, solo_first, solo_last, 0);
> 
>    executes "last->next = old_head.top", i.e. a NULL pointer write.  It
>    also splices free_solo's current head onto pile->solo.
> 
> Reachable path with BULK_SIZE 32: pop of 64 objects, pile->bulk holds
> one element, pile->solo empty, free_solo non-empty.  The pop of 2 bulks
> fails (n_solo becomes 32, n_bulk 1), the pop of 1 bulk succeeds, the
> solo pop of 32 fails, and the fragmentation path is entered with
> n_solo == 32.
> 
> The fragmentation path only makes sense for a partial bulk.  After the
> retry loop, split n_solo back into whole bulks plus a remainder and
> service the whole-bulk part from pile->solo (or fail), keeping the
> fragmentation path bounded to n_solo < RTE_STACK_PILE_BULK_SIZE.
> 
> 2. lib/stack/rte_stack_pile.h, __rte_stack_pile_pop()
> 
> 	__rte_assume(RTE_STACK_PILE_BULK_SIZE - n_solo <
> RTE_STACK_PILE_BULK_SIZE - 1);
> 
> Off by one: with n_solo == 1 the left side is BULK_SIZE - 1, and
> "BULK_SIZE - 1 < BULK_SIZE - 1" is false.  A false __rte_assume() is
> undefined behavior.  Should be "< RTE_STACK_PILE_BULK_SIZE".
> 
> 3. app/test/test_stack.c, test_stack_push_pop()
> 
> 	if (memcmp(&obj_table[i],
> 			&popped_objs[STACK_SIZE - RTE_STACK_PILE_BULK_SIZE -
> i],
> 			RTE_STACK_PILE_BULK_SIZE) != 0) {
> 
> memcmp() takes a byte count, but RTE_STACK_PILE_BULK_SIZE is an object
> count.  Only the first 4 pointers of each 32-pointer bulk are compared
> on a 64-bit build.  Needs
> "RTE_STACK_PILE_BULK_SIZE * sizeof(void *)".
> 
> 4. lib/mempool/rte_mempool.h, rte_mempool_do_generic_put()
> 
> 	const size_t move = RTE_ALIGN_MUL_CEIL(
> 			sizeof(void *) * (cache->len - cache->size / 2), 32);
> 	rte_memcpy(cache->objs, __rte_assume_cache_aligned(&cache-
> >objs[cache->size / 2]),
> 			move);
> 
> Both the alignment hint and the rounded-up length are only valid when
> cache->size is a multiple of 32.  rte_mempool_create_empty() now
> enforces
> that, but rte_mempool_cache_create() is unchanged and still accepts any
> size in 1..RTE_MEMPOOL_CACHE_MAX_SIZE.  A user cache of, say, size 100
> gives &objs[50] at a 400-byte offset, and __builtin_assume_aligned() is
> then told a false precondition - the compiler may emit aligned vector
> loads and fault.  Either apply the same rounding/rejection in
> rte_mempool_cache_create(), or drop the alignment hint.
> 
> Warnings
> 
> 5. ABI and API changes without deprecation notices
> 
> deprecation.rst currently covers only the flushthresh field and the
> oversize objs array.  The patch additionally changes:
> 
>  - struct rte_mempool: local_cache from pointer to inline
>    local_cache[RTE_MAX_LCORE] array
>  - removal of the RTE_MEMPOOL_HEADER_SIZE() macro
>  - RTE_MEMPOOL_CACHE_MAX_SIZE 512 -> 1024
>  - RTE_MEMPOOL_MAX_OPS_IDX 16 -> 32, which changes the size of the
>    exported rte_mempool_ops_table variable
>  - cache_size must now be a multiple of 32
> 
> The two existing deprecation entries should also be removed by this
> patch once they are implemented.
> 
> 6. lib/mempool/rte_mempool.h - mempool header footprint
> 
> With local_cache[] inline and RTE_MEMPOOL_CACHE_MAX_SIZE at 1024, the
> header is roughly RTE_MAX_LCORE * 8.3 KB, i.e. about 1 MB per mempool,
> and it is now allocated (and memset) unconditionally.  Previously
> RTE_MEMPOOL_HEADER_SIZE(mp, 0) omitted the array entirely for mempools
> created with cache_size == 0, which is common for control-object pools.
> 
> 7. lib/mempool/rte_mempool.c, rte_mempool_create_empty()
> 
> 	if (cache_size & 31) {
> 		unsigned int rounded = RTE_ALIGN_MUL_FLOOR(cache_size, 32);
> 
> Any requested cache_size below 32 is silently rounded to 0, disabling
> the cache.  The doxygen change in rte_mempool.h says the argument "must
> be divisible by 32", which reads as a rejection, not a silent rounding
> -
> doc and code disagree.  The log messages should also print the
> requested
> and effective values, and "divisble" is misspelled.
> 
> 8. app/test/test_stack.c
> 
> The excess-push test is wrapped in "#if 0 /* FIXME ... */".  That
> removes coverage for the standard and lock-free stacks as well, and it
> is exactly the test that would exercise the pile capacity behavior in
> item 9.  Dead code should not be committed; either fix the
> -Warray-bounds trigger (a runtime-computed size in a volatile variable
> is usually enough) or drop the block and note the gap in the commit
> message.
> 
> 9. lib/stack/rte_stack_pile.c, rte_stack_pile_init()
> 
> The pile is initialized with ceil(count / BULK_SIZE) bulk elements plus
> count solo elements, so it can hold up to roughly 2 * count objects,
> above the declared capacity.  __rte_stack_pile_count() only hides this
> by clamping with RTE_MIN(s->capacity, ...).  rte_stack_push() is
> documented to fail when there is insufficient space; either enforce the
> capacity or document that the pile does not.
> 
> 10. lib/stack/rte_stack_pile.c
> 
> rte_stack_pile_init() derives the element base from
> "(&s->stack_pile + 1)" while rte_stack_pile_get_memsize() sizes it from
> "sizeof(struct rte_stack)".  These agree only because rte_stack_pile
> happens to be the largest member of the union in struct rte_stack.  If
> another member grows, init() writes past the memzone with no
> diagnostic.
> Use the same expression in both places, or give struct rte_stack_pile a
> flexible array member as rte_stack_lf has.
> 
> 11. lib/stack/rte_stack_pile.h - doxygen
> 
> Both __rte_stack_pile_push() and __rte_stack_pile_pop() document
> "@param pile", but the parameter is "struct rte_stack *s".  Doxygen
> with
> -Dwerror will flag the undocumented parameter.
> 
> 12. Missing release notes
> 
> doc/guides/rel_notes/release_26_11.rst is not updated for the new pile
> stack type, the new "pile" mempool driver, the mempool ABI changes, or
> the new __rte_assume_aligned() / __rte_assume_cache_aligned() EAL
> macros.
> 
> 13. doc/guides/mempool/stack.rst
> 
> The driver guide lists the "stack" and "lf_stack" modes; the new "pile"
> mode is not added.  PMD/driver documentation must match the registered
> ops.
> 
> 14. lib/mempool/mempool_trace.h
> 
> Dropping rte_trace_point_emit_u32(cache->flushthresh) changes the
> recorded trace format for that trace point.  Worth a release note entry
> for consumers parsing the trace output.
> 
> 15. app/test/test_stack_perf.c
> 
> 	#define MAX_BURST (RTE_MEMPOOL_CACHE_MAX_SIZE / 2)
> 
> A stack library test should not take its burst size from a mempool
> configuration constant.  Use a stack-specific value (or
> RTE_STACK_PILE_BULK_SIZE multiples).
> 
> Info
> 
> 16. lib/eal/x86/include/rte_memcpy.h
> 
> The new constant-size block allows n <= 512 for AVX-512 and for SSE,
> but
> only n <= 256 for AVX2 - is the asymmetry intended?  Splitting a single
> "if (" across #if/#elif/#else with the body outside is also hard to
> read; a per-ISA RTE_MEMCPY_CONST_MAX define and one "if" would be
> clearer.  This change and the __rte_assume_aligned() addition are
> independent of the pile work and are good candidates for their own
> patches when the series is split.
> 
> 17. drivers/net/sxe2/sxe2_txrx_vec_avx512.c
> 
> The hunk adds an unrelated blank line before "goto done;".
> 
> 18. lib/stack/rte_stack_pile.h, __rte_stack_pile_bulk_pop_elems()
> 
> The element list is traversed twice: once inside
> __rte_stack_lf_pop_elems() (to find the new head and set *last) and
> again to copy the bulk contents.  For a pop of 8 bulk elements that is
> two dependent pointer chases over the same cache lines.
> 
> 19. drivers/mempool/stack/rte_mempool_stack.c
> 
> pile_enqueue() returns -ENOBUFS when the push fails, but
> rte_mempool_ops_enqueue_bulk() returns void and callers do not recover,
> so a failed put loses objects.  This is the same hazard the lock-free
> stack already has, but the pile has two independent free lists, so the
> window in which a concurrent pop leaves neither free_bulk nor free_solo
> able to satisfy a push is wider.  Worth calling out in stack_lib.rst.
> 
> 20. doc/guides/prog_guide/stack_lib.rst
> 
> "performaing" -> "performing".  The trailing "Note:" paragraph would
> render better as a ".. note::" directive.
> 
> 21. The series mixes at least five independent changes (EAL assume-
> aligned
> macro, x86 rte_memcpy fast path, mempool cache/header rework, the pile
> stack, the pile mempool driver).  You already noted this; those look
> like the natural split points, and the mempool cache rework in
> particular deserves its own review thread given the ABI impact.

  reply	other threads:[~2026-08-02 16:46 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-01  7:15 [RFC PATCH] NEW: pile stack and mempool driver Morten Brørup
2026-08-01 10:09 ` [RFC PATCH v2] " Morten Brørup
2026-08-01 10:17 ` [RFC PATCH v3] " Morten Brørup
2026-08-01 11:13 ` [RFC PATCH v4] " Morten Brørup
2026-08-02  7:24 ` [RFC PATCH v5] " Morten Brørup
2026-08-02  9:59 ` [RFC PATCH v6] " Morten Brørup
2026-08-02 15:22   ` Stephen Hemminger
2026-08-02 16:45     ` Morten Brørup [this message]
2026-08-03  8:14 ` [RFC PATCH v7] " Morten Brørup
2026-08-03 23:02   ` Stephen Hemminger
2026-08-10  9:02 ` [RFC PATCH v8] " Morten Brørup
2026-08-10 13:58 ` [RFC PATCH v9] " Morten Brørup
2026-08-10 15:12 ` [RFC PATCH v10] " Morten Brørup
2026-08-10 16:15   ` Stephen Hemminger
2026-08-10 18:36   ` Morten Brørup

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=98CBD80474FA8B44BF855DF32C47DC35F659B2@smartserver.smartshare.dk \
    --to=mb@smartsharesystems.com \
    --cc=dev@dpdk.org \
    --cc=stephen@networkplumber.org \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.