Re: [RFC PATCH v7] pile stack and mempool driver

Stephen Hemminger <[email protected]> Mon, 3 Aug 2026 16:02:25 -0700
Newsgroups org.dpdk.dev
Message-ID <[email protected]>
On Mon,  3 Aug 2026 08:14:46 +0000
Morten Br=C3=B8rup <[email protected]> wrote:

> Early submission of:
> - A new "pile" stack-like implementation using the Stack API, and
> - an accompanying "pile" mempool driver.
> And:
> - Some mempool optimizations.
> - Deprecating "__rte_restrict" in favor of keyword "__restrict",
>   supported by all relevant C/C++ compilers.
> - An x86 rte_memcpy() optimization for some compile time known sizes
>   (64-byte blocks up to 512 or 256 bytes).
> - Decorating the object tables in the Stack API with "restrict".
>=20
> For CI test and community feedback.
>=20
> Needless to say, this must be separated into a series of patches,
> or multiple independent series of patches.
> For now, I'm submitting a snapshot of work in progress.
>=20
> The "pile" somewhat resembles the lock-free stack, but operates on
> bulks (arrays) of objects, to significantly reduce linked list
> traversal.
> With the pile's default bulk size of 32 objects, a mempool cache
> flush/refill traverses a linked list of only 16 elements, whereas
> the lock-free stack would traverse a linked list of 512 elements.
>=20
> Some performance numbers from mempool_perf_autotest_2cores, all
> with cache=3D1024 cores=3D2 n_keep=3D32768:
>=20
> start performance test (using ring_mp_mc, with cache)
> n_get_bulk=3D 64 n_put_bulk=3D 64 constant_n=3D0 rate_persec=3D 753985338
> n_get_bulk=3D256 n_put_bulk=3D256 constant_n=3D0 rate_persec=3D 755805913
>=20
> start performance test for lf_stack (with cache)
> n_get_bulk=3D 64 n_put_bulk=3D 64 constant_n=3D0 rate_persec=3D  29132352
> n_get_bulk=3D256 n_put_bulk=3D256 constant_n=3D0 rate_persec=3D  29276708
>=20
> start performance test for pile (with cache)
> n_get_bulk=3D 64 n_put_bulk=3D 64 constant_n=3D0 rate_persec=3D 560159479
> n_get_bulk=3D256 n_put_bulk=3D256 constant_n=3D0 rate_persec=3D 557910933
>=20
> Hat tip to Bruce for bringing attention to the ring not being the
> optimal mempool driver!
>=20
> Signed-off-by: Morten Br=C3=B8rup <[email protected]>
> ---

Claude Opus AI review still finds lots of issues.

Review of [RFC PATCH v7] pile stack and mempool driver

Fixed since v6: memcmp() size in test_stack_push_pop(), the off-by-one
__rte_assume() in the fragmentation path, the n_solo >=3D BULK_SIZE
overrun (now guarded), rte_mempool_cache_create() alignment
precondition, the init/get_memsize base-address mismatch (flexible
array member), the @param names, the rte_memcpy #if/#elif split, the
sxe2 stray blank line, the doc typo and the note directive.

Errors

1. lib/stack/rte_stack_pile.h, __rte_stack_pile_pop(), "fail:" label

	if (n_bulk > 0) {
		/* Attach the fragmentation element after the bulk elements. */
		bulk_last->next =3D frag;
	} else {
		bulk_first =3D frag;
		bulk_last =3D frag;
	}
	n_bulk +=3D 1;
	...
	__rte_stack_pile_bulk_push_elems(&pile->bulk, bulk_first, bulk_last, n_bul=
k);

In the n_bulk > 0 branch, frag is linked after bulk_last but bulk_last
is not advanced to frag.  __rte_stack_lf_push_elems() then executes
"last->next =3D old_head.top" on the old bulk_last, overwriting the link
to frag.  frag is dropped from every list, while list->len is
incremented by n_bulk + 1.

Two consequences: the bulk element and its RTE_STACK_PILE_BULK_SIZE
objects are leaked permanently, and pile->bulk.len is now one higher
than the number of linked elements.  A later pop that reserves that
phantom element will succeed in the CAS on len, then walk off the end
of the list, hit "i !=3D num", re-read list->head and retry - forever.
The inflated len never recovers, so this is a hang, not a transient
retry.

This is not new in v7; it was present in v6 and I missed it.

Easiest fix is to mirror what the success path already does correctly
and prepend instead of append:

	if (frag !=3D NULL) {
		if (n_bulk > 0)
			frag->next =3D bulk_first;
		else
			bulk_last =3D frag;
		bulk_first =3D frag;
		n_bulk++;
	}

2. lib/stack/rte_stack_pile.c, rte_stack_pile_get_memsize()

	sz +=3D count * sizeof(struct rte_stack_lf_elem);
	sz +=3D RTE_CACHE_LINE_ROUNDUP(sz);

RTE_CACHE_LINE_ROUNDUP() returns the rounded value, it does not return
the padding, so "+=3D" roughly doubles the allocation instead of rounding
it up.  Every pile stack, and therefore every pile mempool, reserves
about twice the memzone it needs.  Should be:

	sz =3D RTE_CACHE_LINE_ROUNDUP(sz);

3. doc/guides/mempool/stack.rst

	For more information please refer to :ref:`_Stack_Library_Pile`.

The leading underscore belongs to the label definition, not the
reference; compare the existing ":ref:`Stack_Library_LF_Stack`" two
paragraphs above.  As written, Sphinx cannot resolve the reference,
which fails the docs build with -Dwerror=3Dtrue.

Warnings

4. __rte_restrict deprecation

Replacing __rte_restrict with the raw __restrict compiler extension and
adding a checkpatches.sh rule against the RTE macro is a project-wide
policy change and should be a separate patch on its own thread, not a
side effect of the pile work.  Specific problems as posted:

 - The tree still has about 150 uses of __rte_restrict in 10 files that
   this patch does not convert, 98 of them in drivers/net/mlx5/mlx5_tx.h.
   The new checkpatch rule will fire on unrelated patches that touch any
   of those lines.
 - The deprecation is announced only in a doxygen comment.  There is no
   entry in doc/guides/rel_notes/deprecation.rst, no release note, and
   the macro is not marked with RTE_DEPRECATED, so nothing warns at
   compile time.
 - The checkpatch message says "prefer restrict", but the patch uses
   "__restrict".  "restrict" is the C99/C11 keyword; "__restrict" is a
   compiler extension.  Picking between the two per language mode is
   exactly what the macro existed for, since C++ has no "restrict".  If
   the intent is to standardise on the extension, the message and the
   doxygen text should say so.

5. Test-only configuration changes

config/rte_config.h sets RTE_MBUF_DEFAULT_MEMPOOL_OPS to "pile" and
config/x86/meson.build sets RTE_USE_C11_MEM_MODEL.  Both are marked
FIXME/test-only, noting them so they are not forgotten before the
series is split and reposted.

6. lib/stack/rte_stack_pile.h

The "return 0;" after the fail label is indented with four spaces
instead of a tab.

7. rte_mempool_cache_create() and rte_mempool_create_empty() now
disagree

Both round cache_size down to a multiple of 32, but for a request below
32 rte_mempool_cache_create() returns EINVAL while
rte_mempool_create_empty() logs a warning and silently creates the
mempool with no cache at all.  Same input, two different outcomes.  The
doxygen for the cache_size argument says it "must be divisible by 32",
which reads as a rejection in both cases.

The silent path is the risky one: any existing caller passing a small
cache size loses caching entirely with no error.  The TAP change in this
patch (TAP_GSO_MBUF_CACHE_SIZE 4 -> 32) is one instance that was caught;
call sites passing literals are not.  Consider rejecting in both places,
or rounding up to 32 rather than down to 0.  The log messages should
also print the requested and effective values.

8. ABI and API changes still without deprecation notices

deprecation.rst is untouched.  Still outstanding from v6:

 - struct rte_mempool: local_cache pointer -> inline
   local_cache[RTE_MAX_LCORE] array
 - removal of RTE_MEMPOOL_HEADER_SIZE()
 - 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
 - the new divisible-by-32 requirement on cache_size

The two entries that this patch does implement (flushthresh, oversize
objs array) should be deleted from deprecation.rst by the patch that
implements them.

9. Mempool header footprint (unchanged from v6)

With local_cache[] inline and RTE_MEMPOOL_CACHE_MAX_SIZE at 1024 the
header is roughly 1 MB per mempool, now allocated and memset
unconditionally.  Previously RTE_MEMPOOL_HEADER_SIZE(mp, 0) omitted the
array entirely for mempools created with cache_size =3D=3D 0.

10. app/test/test_stack.c (unchanged from v6)

The excess-push test is still inside "#if 0 /* FIXME ... */", which
removes the check for the standard and lock-free stacks as well.

11. Pile capacity exceeds the requested count (unchanged from v6)

rte_stack_pile_init() creates ceil(count / BULK_SIZE) bulk elements plus
count solo elements, so the pile can hold roughly 2 * count objects.
__rte_stack_pile_count() hides this 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.

12. Missing release notes

doc/guides/rel_notes/release_26_11.rst is still not updated for the new
pile stack type, the new "pile" mempool driver, the mempool ABI changes,
the new __rte_assume_aligned()/__rte_assume_cache_aligned() macros, or
the __rte_restrict deprecation.

13. lib/mempool/mempool_trace.h (unchanged from v6)

Dropping the flushthresh field changes the recorded format of that trace
point; worth a release note for consumers parsing trace output.

14. app/test/test_stack_perf.c (unchanged from v6)

MAX_BURST is still derived from RTE_MEMPOOL_CACHE_MAX_SIZE.  A stack
library test should not take its burst size from a mempool
configuration constant.

Info

15. lib/stack/rte_stack_pile.c

	(struct rte_stack_pile_bulk_elem *)(&s->stack_pile.elems)

"&array" where "array" is meant; the cast hides the type mismatch.
s->stack_pile.elems (or &s->stack_pile.elems[0]) is clearer.

16. lib/stack/rte_stack_pile.h

Hoisting obj_frag[] to the top of __rte_stack_pile_pop() (for the C23
label/declaration issue) puts 256 bytes at cache-line alignment in the
frame of a function that is __rte_always_inline into the mempool
dequeue fast path, whether or not fragmentation is reached.  A nested
block around the fragmentation code would keep it off the fast path
while still avoiding a declaration after a label.

17. lib/stack/rte_stack_pile.h, __rte_stack_pile_bulk_pop_elems()

The element chain is still traversed twice: once inside
__rte_stack_lf_pop_elems() to find the new head, and again to copy the
bulk contents.

18. drivers/mempool/stack/rte_mempool_stack.c

pile_enqueue() returns -ENOBUFS on failure, but
rte_mempool_ops_enqueue_bulk() returns void, so a failed put loses
objects.  The lock-free stack has the same hazard, but the pile draws
from two independent free lists, so the window in which a concurrent pop
leaves neither able to satisfy a push is wider.  Worth documenting in
stack_lib.rst.

19. The series still mixes independent changes (the __rte_restrict and
x86 rte_memcpy work, the mempool cache/header rework, the pile stack,
the pile mempool driver).  The mempool cache rework in particular
deserves its own thread given the ABI impact.