* [PATCH] mbuf: avoid temporary array for bulk free
@ 2026-09-22 1:28 Nam Tran
2026-09-22 10:59 ` Morten Brørup
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Nam Tran @ 2026-09-22 1:28 UTC (permalink / raw)
To: Morten Brørup; +Cc: dev, Nam Tran
rte_pktmbuf_free_bulk() currently stages freeable mbufs in a
temporary array before returning them to their mempool. For flat
packet arrays, this requires copying pointers even though the
original array already contains contiguous freeable mbufs.
Track contiguous same-pool runs in the input array and pass them
directly to rte_mbuf_raw_free_bulk(). Flush a run when encountering
a NULL mbuf, an mbuf retained by reference counting, or a pool
change. Preserve the existing array-based implementation as the
fallback for chained packets.
On an ARM64 Linux test environment, same-binary A/B measurements
using rte_rdtsc showed lower median timer ticks per call for flat
bulk frees:
burst 32: 2.05 -> 1.50
burst 64: 5.16 -> 4.52
burst 128: 11.16 -> 6.90
burst 256: 26.46 -> 19.73
This corresponds to reductions of approximately 12% to 38% across
the tested burst sizes.
Add coverage for NULL entries, mixed mempools, and shared mbufs.
Signed-off-by: Nam Tran <hoangnamtran18122005@gmail.com>
---
app/test/test_mbuf.c | 53 +++++++++++++++++++++++++++++++++
lib/mbuf/rte_mbuf.c | 71 ++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 121 insertions(+), 3 deletions(-)
diff --git a/app/test/test_mbuf.c b/app/test/test_mbuf.c
index db23259745..2aed37b225 100644
--- a/app/test/test_mbuf.c
+++ b/app/test/test_mbuf.c
@@ -833,6 +833,59 @@ test_pktmbuf_pool_bulk(void)
goto err;
}
+ printf("Test bulk free with NULL entries.\n");
+
+ ret = rte_pktmbuf_alloc_bulk(pool, mbufs, 4);
+ if (ret != 0)
+ goto err;
+
+ m = mbufs[1];
+ mbufs[1] = NULL;
+ rte_pktmbuf_free_bulk(mbufs, 4);
+ rte_pktmbuf_free(m);
+
+ if (!rte_mempool_full(pool)) {
+ printf("mempool not full after NULL-entry bulk free\n");
+ goto err;
+ }
+
+ printf("Test bulk free with multiple pools.\n");
+
+ for (i = 0; i < 4; i++) {
+ mbufs[i] = rte_pktmbuf_alloc((i & 1) ? pool2 : pool);
+ if (mbufs[i] == NULL)
+ goto err;
+ }
+
+ rte_pktmbuf_free_bulk(mbufs, 4);
+
+ if (!(rte_mempool_full(pool) && rte_mempool_full(pool2))) {
+ printf("mempools not full after mixed-pool bulk free\n");
+ goto err;
+ }
+
+ printf("Test bulk free with shared mbuf.\n");
+
+ ret = rte_pktmbuf_alloc_bulk(pool, mbufs, 4);
+ if (ret != 0)
+ goto err;
+
+ m = mbufs[1];
+ rte_mbuf_refcnt_update(m, 1);
+ rte_pktmbuf_free_bulk(mbufs, 4);
+
+ if (rte_mbuf_refcnt_read(m) != 1) {
+ printf("shared mbuf reference count incorrect\n");
+ goto err;
+ }
+
+ rte_pktmbuf_free(m);
+
+ if (!rte_mempool_full(pool)) {
+ printf("mempool not full after shared mbuf free\n");
+ goto err;
+ }
+
printf("Test bulk free of single long chain.\n");
/* Bulk allocate all mbufs in the pool, in one go. */
diff --git a/lib/mbuf/rte_mbuf.c b/lib/mbuf/rte_mbuf.c
index 005bfaa573..796631a033 100644
--- a/lib/mbuf/rte_mbuf.c
+++ b/lib/mbuf/rte_mbuf.c
@@ -555,15 +555,15 @@ __rte_pktmbuf_free_seg_via_array(struct rte_mbuf *m,
*/
#define RTE_PKTMBUF_FREE_PENDING_SZ 64
-/* Free a bulk of packet mbufs back into their original mempools. */
-RTE_EXPORT_SYMBOL(rte_pktmbuf_free_bulk)
-void rte_pktmbuf_free_bulk(struct rte_mbuf **mbufs, unsigned int count)
+static void
+__rte_pktmbuf_free_bulk_fallback(struct rte_mbuf **mbufs, unsigned int count)
{
struct rte_mbuf *m, *m_next, *pending[RTE_PKTMBUF_FREE_PENDING_SZ];
unsigned int idx, nb_pending = 0;
for (idx = 0; idx < count; idx++) {
m = mbufs[idx];
+
if (unlikely(m == NULL))
continue;
@@ -582,6 +582,71 @@ void rte_pktmbuf_free_bulk(struct rte_mbuf **mbufs, unsigned int count)
rte_mbuf_raw_free_bulk(pending[0]->pool, pending, nb_pending);
}
+/* Free a bulk of packet mbufs back into their original mempools. */
+RTE_EXPORT_SYMBOL(rte_pktmbuf_free_bulk)
+void rte_pktmbuf_free_bulk(struct rte_mbuf **mbufs, unsigned int count)
+{
+ struct rte_mempool *run_pool = NULL;
+ unsigned int run_start = 0;
+ unsigned int run_count = 0;
+ unsigned int idx;
+
+ for (idx = 0; idx < count; idx++) {
+ struct rte_mbuf *m = mbufs[idx];
+
+ if (unlikely(m == NULL)) {
+ if (run_count != 0) {
+ rte_mbuf_raw_free_bulk(run_pool,
+ &mbufs[run_start], run_count);
+ run_count = 0;
+ }
+ continue;
+ }
+
+ __rte_mbuf_sanity_check(m, 1);
+
+ /*
+ * Preserve the generic path for chained packets. No mbuf in
+ * this suffix has been modified yet.
+ */
+ if (unlikely(m->next != NULL)) {
+ if (run_count != 0)
+ rte_mbuf_raw_free_bulk(run_pool,
+ &mbufs[run_start], run_count);
+
+ __rte_pktmbuf_free_bulk_fallback(&mbufs[idx],
+ count - idx);
+ return;
+ }
+
+ m = rte_pktmbuf_prefree_seg(m);
+ if (unlikely(m == NULL)) {
+ if (run_count != 0) {
+ rte_mbuf_raw_free_bulk(run_pool,
+ &mbufs[run_start], run_count);
+ run_count = 0;
+ }
+ continue;
+ }
+
+ if (run_count != 0 && m->pool != run_pool) {
+ rte_mbuf_raw_free_bulk(run_pool,
+ &mbufs[run_start], run_count);
+ run_count = 0;
+ }
+
+ if (run_count == 0) {
+ run_pool = m->pool;
+ run_start = idx;
+ }
+
+ run_count++;
+ }
+
+ if (run_count != 0)
+ rte_mbuf_raw_free_bulk(run_pool, &mbufs[run_start], run_count);
+}
+
/* Creates a shallow copy of mbuf */
RTE_EXPORT_SYMBOL(rte_pktmbuf_clone)
struct rte_mbuf *
--
2.51.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* RE: [PATCH] mbuf: avoid temporary array for bulk free
2026-09-22 1:28 [PATCH] mbuf: avoid temporary array for bulk free Nam Tran
@ 2026-09-22 10:59 ` Morten Brørup
2026-09-22 13:54 ` Stephen Hemminger
2026-09-29 0:25 ` [PATCH v2] " Nam Tran
2 siblings, 0 replies; 7+ messages in thread
From: Morten Brørup @ 2026-09-22 10:59 UTC (permalink / raw)
To: Nam Tran; +Cc: dev
> From: Nam Tran [mailto:hoangnamtran18122005@gmail.com]
> Sent: Tuesday, 22 September 2026 03.29
>
> rte_pktmbuf_free_bulk() currently stages freeable mbufs in a
> temporary array before returning them to their mempool. For flat
> packet arrays, this requires copying pointers even though the
> original array already contains contiguous freeable mbufs.
>
> Track contiguous same-pool runs in the input array and pass them
> directly to rte_mbuf_raw_free_bulk(). Flush a run when encountering
> a NULL mbuf, an mbuf retained by reference counting, or a pool
> change. Preserve the existing array-based implementation as the
> fallback for chained packets.
>
> On an ARM64 Linux test environment, same-binary A/B measurements
> using rte_rdtsc showed lower median timer ticks per call for flat
> bulk frees:
>
> burst 32: 2.05 -> 1.50
> burst 64: 5.16 -> 4.52
> burst 128: 11.16 -> 6.90
> burst 256: 26.46 -> 19.73
>
> This corresponds to reductions of approximately 12% to 38% across
> the tested burst sizes.
>
> Add coverage for NULL entries, mixed mempools, and shared mbufs.
>
> Signed-off-by: Nam Tran <hoangnamtran18122005@gmail.com>
Good idea.
With the changes described inline below,
Reviewed-by: Morten Brørup <mb@smartsharesystems.com>
> ---
> app/test/test_mbuf.c | 53 +++++++++++++++++++++++++++++++++
> lib/mbuf/rte_mbuf.c | 71 ++++++++++++++++++++++++++++++++++++++++++--
> 2 files changed, 121 insertions(+), 3 deletions(-)
>
> diff --git a/app/test/test_mbuf.c b/app/test/test_mbuf.c
> index db23259745..2aed37b225 100644
> --- a/app/test/test_mbuf.c
> +++ b/app/test/test_mbuf.c
> @@ -833,6 +833,59 @@ test_pktmbuf_pool_bulk(void)
> goto err;
> }
>
> + printf("Test bulk free with NULL entries.\n");
> +
> + ret = rte_pktmbuf_alloc_bulk(pool, mbufs, 4);
> + if (ret != 0)
> + goto err;
> +
> + m = mbufs[1];
> + mbufs[1] = NULL;
> + rte_pktmbuf_free_bulk(mbufs, 4);
> + rte_pktmbuf_free(m);
> +
> + if (!rte_mempool_full(pool)) {
> + printf("mempool not full after NULL-entry bulk free\n");
> + goto err;
> + }
> +
> + printf("Test bulk free with multiple pools.\n");
> +
> + for (i = 0; i < 4; i++) {
> + mbufs[i] = rte_pktmbuf_alloc((i & 1) ? pool2 : pool);
> + if (mbufs[i] == NULL)
> + goto err;
> + }
> +
> + rte_pktmbuf_free_bulk(mbufs, 4);
> +
> + if (!(rte_mempool_full(pool) && rte_mempool_full(pool2))) {
> + printf("mempools not full after mixed-pool bulk free\n");
> + goto err;
> + }
> +
> + printf("Test bulk free with shared mbuf.\n");
> +
> + ret = rte_pktmbuf_alloc_bulk(pool, mbufs, 4);
> + if (ret != 0)
> + goto err;
> +
> + m = mbufs[1];
> + rte_mbuf_refcnt_update(m, 1);
> + rte_pktmbuf_free_bulk(mbufs, 4);
> +
> + if (rte_mbuf_refcnt_read(m) != 1) {
> + printf("shared mbuf reference count incorrect\n");
> + goto err;
> + }
> +
> + rte_pktmbuf_free(m);
> +
> + if (!rte_mempool_full(pool)) {
> + printf("mempool not full after shared mbuf free\n");
> + goto err;
> + }
> +
> printf("Test bulk free of single long chain.\n");
>
> /* Bulk allocate all mbufs in the pool, in one go. */
> diff --git a/lib/mbuf/rte_mbuf.c b/lib/mbuf/rte_mbuf.c
> index 005bfaa573..796631a033 100644
> --- a/lib/mbuf/rte_mbuf.c
> +++ b/lib/mbuf/rte_mbuf.c
> @@ -555,15 +555,15 @@ __rte_pktmbuf_free_seg_via_array(struct rte_mbuf
> *m,
> */
> #define RTE_PKTMBUF_FREE_PENDING_SZ 64
>
> -/* Free a bulk of packet mbufs back into their original mempools. */
> -RTE_EXPORT_SYMBOL(rte_pktmbuf_free_bulk)
> -void rte_pktmbuf_free_bulk(struct rte_mbuf **mbufs, unsigned int
> count)
> +static void
> +__rte_pktmbuf_free_bulk_fallback(struct rte_mbuf **mbufs, unsigned int
> count)
> {
> struct rte_mbuf *m, *m_next,
> *pending[RTE_PKTMBUF_FREE_PENDING_SZ];
> unsigned int idx, nb_pending = 0;
>
> for (idx = 0; idx < count; idx++) {
> m = mbufs[idx];
> +
Don't add empty line here.
> if (unlikely(m == NULL))
> continue;
>
> @@ -582,6 +582,71 @@ void rte_pktmbuf_free_bulk(struct rte_mbuf
> **mbufs, unsigned int count)
> rte_mbuf_raw_free_bulk(pending[0]->pool, pending,
> nb_pending);
> }
>
> +/* Free a bulk of packet mbufs back into their original mempools. */
> +RTE_EXPORT_SYMBOL(rte_pktmbuf_free_bulk)
> +void rte_pktmbuf_free_bulk(struct rte_mbuf **mbufs, unsigned int
> count)
> +{
> + struct rte_mempool *run_pool = NULL;
> + unsigned int run_start = 0;
> + unsigned int run_count = 0;
> + unsigned int idx;
> +
> + for (idx = 0; idx < count; idx++) {
> + struct rte_mbuf *m = mbufs[idx];
> +
> + if (unlikely(m == NULL)) {
> + if (run_count != 0) {
> + rte_mbuf_raw_free_bulk(run_pool,
> + &mbufs[run_start], run_count);
> + run_count = 0;
> + }
> + continue;
> + }
> +
> + __rte_mbuf_sanity_check(m, 1);
Move the sanity check down after the fallback path;
otherwise, it gets run twice for the current mbuf (here, and in the fallback path).
> +
> + /*
> + * Preserve the generic path for chained packets. No mbuf in
> + * this suffix has been modified yet.
> + */
> + if (unlikely(m->next != NULL)) {
> + if (run_count != 0)
> + rte_mbuf_raw_free_bulk(run_pool,
> + &mbufs[run_start], run_count);
> +
> + __rte_pktmbuf_free_bulk_fallback(&mbufs[idx],
> + count - idx);
> + return;
> + }
Move the sanity check to here.
> +
> + m = rte_pktmbuf_prefree_seg(m);
> + if (unlikely(m == NULL)) {
> + if (run_count != 0) {
> + rte_mbuf_raw_free_bulk(run_pool,
> + &mbufs[run_start], run_count);
> + run_count = 0;
> + }
> + continue;
> + }
> +
> + if (run_count != 0 && m->pool != run_pool) {
> + rte_mbuf_raw_free_bulk(run_pool,
> + &mbufs[run_start], run_count);
> + run_count = 0;
> + }
> +
> + if (run_count == 0) {
> + run_pool = m->pool;
> + run_start = idx;
> + }
> +
> + run_count++;
> + }
> +
> + if (run_count != 0)
> + rte_mbuf_raw_free_bulk(run_pool, &mbufs[run_start],
> run_count);
> +}
> +
> /* Creates a shallow copy of mbuf */
> RTE_EXPORT_SYMBOL(rte_pktmbuf_clone)
> struct rte_mbuf *
> --
> 2.51.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] mbuf: avoid temporary array for bulk free
2026-09-22 1:28 [PATCH] mbuf: avoid temporary array for bulk free Nam Tran
2026-09-22 10:59 ` Morten Brørup
@ 2026-09-22 13:54 ` Stephen Hemminger
2026-09-29 0:25 ` [PATCH v2] " Nam Tran
2 siblings, 0 replies; 7+ messages in thread
From: Stephen Hemminger @ 2026-09-22 13:54 UTC (permalink / raw)
To: Nam Tran; +Cc: Morten Brørup, dev
On Mon, 21 Sep 2026 21:28:56 -0400
Nam Tran <hoangnamtran18122005@gmail.com> wrote:
> rte_pktmbuf_free_bulk() currently stages freeable mbufs in a
> temporary array before returning them to their mempool. For flat
> packet arrays, this requires copying pointers even though the
> original array already contains contiguous freeable mbufs.
>
> Track contiguous same-pool runs in the input array and pass them
> directly to rte_mbuf_raw_free_bulk(). Flush a run when encountering
> a NULL mbuf, an mbuf retained by reference counting, or a pool
> change. Preserve the existing array-based implementation as the
> fallback for chained packets.
>
> On an ARM64 Linux test environment, same-binary A/B measurements
> using rte_rdtsc showed lower median timer ticks per call for flat
> bulk frees:
>
> burst 32: 2.05 -> 1.50
> burst 64: 5.16 -> 4.52
> burst 128: 11.16 -> 6.90
> burst 256: 26.46 -> 19.73
>
> This corresponds to reductions of approximately 12% to 38% across
> the tested burst sizes.
>
> Add coverage for NULL entries, mixed mempools, and shared mbufs.
>
> Signed-off-by: Nam Tran <hoangnamtran18122005@gmail.com>
> ---
More detailed AI review (Claude Opus 5)
Subject: Re: [PATCH] mbuf: avoid temporary array for bulk free
Warning:
lib/mbuf/rte_mbuf.c: runs are unbounded. The old code flushed at
RTE_PKTMBUF_FREE_PENDING_SZ (64). Now a same-pool run can be the whole
burst, and rte_mempool_do_generic_put() sends any n > cache->size / 2
straight to rte_mempool_ops_enqueue_bulk(), bypassing the per-lcore
cache. With a 256 entry cache, a 256 burst that previously went into
the cache in 64 entry chunks now hits the ring every time. That is
cheap on a single lcore, which is what the benchmark measured, but is
shared ring traffic with multiple lcores, and hands back cold objects
instead of keeping hot ones in cache. Cap run_count at
RTE_PKTMBUF_FREE_PENDING_SZ and flush when reached.
Benchmark: rte_rdtsc() on arm64 reads the generic timer (cntvct_el0)
unless built with PMU support; a delta of ~0.5 ticks per call is at
the resolution limit. Please state the timer frequency, the mempool
cache size used, the number of lcores, and include x86 results. The
test pools in test_mbuf.c have no cache, so they do not exercise the
cache path at all.
Info:
The flush sequence is open coded four times, and
rte_mbuf_raw_free_bulk() is __rte_always_inline, so the function body
grows accordingly. A small static helper or restructuring the loop so
NULL, not-freed, and pool-change share one flush point would be
cleaner.
Tests: add a case that mixes flat and chained packets in one array
(flat run pending when the chain is hit, then flat after it), and one
with an indirect (cloned) mbuf in the flat path. Current tests do not
cover the transition into __rte_pktmbuf_free_bulk_fallback() with a
non-empty run, which is the new logic most likely to break.
Nit: the blank line added after "m = mbufs[idx];" in the fallback is
unrelated churn.
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2] mbuf: avoid temporary array for bulk free
2026-09-22 1:28 [PATCH] mbuf: avoid temporary array for bulk free Nam Tran
2026-09-22 10:59 ` Morten Brørup
2026-09-22 13:54 ` Stephen Hemminger
@ 2026-09-29 0:25 ` Nam Tran
2026-09-29 4:39 ` Morten Brørup
` (2 more replies)
2 siblings, 3 replies; 7+ messages in thread
From: Nam Tran @ 2026-09-29 0:25 UTC (permalink / raw)
To: mb; +Cc: dev, stephen, Nam Tran
rte_pktmbuf_free_bulk() currently stages freeable mbufs in a
temporary array before returning them to their mempool. For flat
packet arrays, this copies pointers even when the input array
already contains contiguous freeable mbufs.
Track contiguous same-pool runs in the input array and pass them
directly to rte_mbuf_raw_free_bulk(). Flush at 64 entries to
preserve the existing mempool-cache behavior. Also flush on NULL
entries, retained mbufs, and pool changes. Keep the array-based
fallback for chained packets.
Add coverage for NULL entries, mixed mempools, shared and indirect
mbufs, and a flat run followed by a chained packet.
Signed-off-by: Nam Tran <hoangnamtran18122005@gmail.com>
Reviewed-by: Morten Brørup <mb@smartsharesystems.com>
---
v2:
- Cap same-pool runs at 64 to retain mempool-cache behavior.
- Move the sanity check after the chained-packet fallback decision.
- Add mixed flat/chained and indirect-mbuf regression cases.
- Remove the earlier timing claim; the capped version has not yet
been benchmarked on x86 or with a cached mempool.
Tested: ARM64 Linux debug build and mbuf_autotest passed.
app/test/test_mbuf.c | 94 ++++++++++++++++++++++++++++++++++++++++++++
lib/mbuf/rte_mbuf.c | 74 ++++++++++++++++++++++++++++++++--
2 files changed, 165 insertions(+), 3 deletions(-)
diff --git a/app/test/test_mbuf.c b/app/test/test_mbuf.c
index db23259745..249e541173 100644
--- a/app/test/test_mbuf.c
+++ b/app/test/test_mbuf.c
@@ -833,6 +833,100 @@ test_pktmbuf_pool_bulk(void)
goto err;
}
+ printf("Test bulk free with NULL entries.\n");
+
+ ret = rte_pktmbuf_alloc_bulk(pool, mbufs, 4);
+ if (ret != 0)
+ goto err;
+
+ m = mbufs[1];
+ mbufs[1] = NULL;
+ rte_pktmbuf_free_bulk(mbufs, 4);
+ rte_pktmbuf_free(m);
+
+ if (!rte_mempool_full(pool)) {
+ printf("mempool not full after NULL-entry bulk free\n");
+ goto err;
+ }
+
+ printf("Test bulk free with multiple pools.\n");
+
+ for (i = 0; i < 4; i++) {
+ mbufs[i] = rte_pktmbuf_alloc((i & 1) ? pool2 : pool);
+ if (mbufs[i] == NULL)
+ goto err;
+ }
+
+ rte_pktmbuf_free_bulk(mbufs, 4);
+
+ if (!(rte_mempool_full(pool) && rte_mempool_full(pool2))) {
+ printf("mempools not full after mixed-pool bulk free\n");
+ goto err;
+ }
+
+ printf("Test bulk free with shared mbuf.\n");
+
+ ret = rte_pktmbuf_alloc_bulk(pool, mbufs, 4);
+ if (ret != 0)
+ goto err;
+
+ m = mbufs[1];
+ rte_mbuf_refcnt_update(m, 1);
+ rte_pktmbuf_free_bulk(mbufs, 4);
+
+ if (rte_mbuf_refcnt_read(m) != 1) {
+ printf("shared mbuf reference count incorrect\n");
+ goto err;
+ }
+
+ rte_pktmbuf_free(m);
+
+ if (!rte_mempool_full(pool)) {
+ printf("mempool not full after shared mbuf free\n");
+ goto err;
+ }
+
+ printf("Test bulk free of flat mbufs around a chain.\n");
+
+ ret = rte_pktmbuf_alloc_bulk(pool, mbufs, 5);
+ if (ret != 0)
+ goto err;
+
+ ret = rte_pktmbuf_chain(mbufs[1], mbufs[2]);
+ if (ret != 0)
+ goto err;
+ mbufs[2] = mbufs[3];
+ mbufs[3] = mbufs[4];
+ rte_pktmbuf_free_bulk(mbufs, 4);
+
+ if (!rte_mempool_full(pool)) {
+ printf("mempool not full after mixed flat and chain bulk free\n");
+ goto err;
+ }
+
+ printf("Test bulk free with an indirect mbuf.\n");
+
+ m = rte_pktmbuf_alloc(pool);
+ if (m == NULL)
+ goto err;
+ mbufs[0] = rte_pktmbuf_alloc(pool);
+ mbufs[1] = rte_pktmbuf_clone(m, pool);
+ mbufs[2] = rte_pktmbuf_alloc(pool);
+ if (mbufs[0] == NULL || mbufs[1] == NULL || mbufs[2] == NULL)
+ goto err;
+
+ rte_pktmbuf_free_bulk(mbufs, 3);
+ if (rte_mbuf_refcnt_read(m) != 1) {
+ printf("indirect mbuf reference count incorrect\n");
+ goto err;
+ }
+ rte_pktmbuf_free(m);
+
+ if (!rte_mempool_full(pool)) {
+ printf("mempool not full after indirect mbuf bulk free\n");
+ goto err;
+ }
+
printf("Test bulk free of single long chain.\n");
/* Bulk allocate all mbufs in the pool, in one go. */
diff --git a/lib/mbuf/rte_mbuf.c b/lib/mbuf/rte_mbuf.c
index 005bfaa573..99b2ea07cf 100644
--- a/lib/mbuf/rte_mbuf.c
+++ b/lib/mbuf/rte_mbuf.c
@@ -555,9 +555,8 @@ __rte_pktmbuf_free_seg_via_array(struct rte_mbuf *m,
*/
#define RTE_PKTMBUF_FREE_PENDING_SZ 64
-/* Free a bulk of packet mbufs back into their original mempools. */
-RTE_EXPORT_SYMBOL(rte_pktmbuf_free_bulk)
-void rte_pktmbuf_free_bulk(struct rte_mbuf **mbufs, unsigned int count)
+static void
+__rte_pktmbuf_free_bulk_fallback(struct rte_mbuf **mbufs, unsigned int count)
{
struct rte_mbuf *m, *m_next, *pending[RTE_PKTMBUF_FREE_PENDING_SZ];
unsigned int idx, nb_pending = 0;
@@ -582,6 +581,75 @@ void rte_pktmbuf_free_bulk(struct rte_mbuf **mbufs, unsigned int count)
rte_mbuf_raw_free_bulk(pending[0]->pool, pending, nb_pending);
}
+/* Free a bulk of packet mbufs back into their original mempools. */
+RTE_EXPORT_SYMBOL(rte_pktmbuf_free_bulk)
+void rte_pktmbuf_free_bulk(struct rte_mbuf **mbufs, unsigned int count)
+{
+ struct rte_mempool *run_pool = NULL;
+ unsigned int run_start = 0;
+ unsigned int run_count = 0;
+ unsigned int idx;
+
+ for (idx = 0; idx < count; idx++) {
+ struct rte_mbuf *m = mbufs[idx];
+
+ if (unlikely(m == NULL)) {
+ if (run_count != 0) {
+ rte_mbuf_raw_free_bulk(run_pool,
+ &mbufs[run_start], run_count);
+ run_count = 0;
+ }
+ continue;
+ }
+
+ /*
+ * Preserve the generic path for chained packets. No mbuf in
+ * this suffix has been modified yet.
+ */
+ if (unlikely(m->next != NULL)) {
+ if (run_count != 0)
+ rte_mbuf_raw_free_bulk(run_pool,
+ &mbufs[run_start], run_count);
+
+ __rte_pktmbuf_free_bulk_fallback(&mbufs[idx],
+ count - idx);
+ return;
+ }
+
+ __rte_mbuf_sanity_check(m, 1);
+ m = rte_pktmbuf_prefree_seg(m);
+ if (unlikely(m == NULL)) {
+ if (run_count != 0) {
+ rte_mbuf_raw_free_bulk(run_pool,
+ &mbufs[run_start], run_count);
+ run_count = 0;
+ }
+ continue;
+ }
+
+ if (run_count != 0 && m->pool != run_pool) {
+ rte_mbuf_raw_free_bulk(run_pool,
+ &mbufs[run_start], run_count);
+ run_count = 0;
+ }
+
+ if (run_count == 0) {
+ run_pool = m->pool;
+ run_start = idx;
+ }
+
+ run_count++;
+ if (run_count == RTE_PKTMBUF_FREE_PENDING_SZ) {
+ rte_mbuf_raw_free_bulk(run_pool,
+ &mbufs[run_start], run_count);
+ run_count = 0;
+ }
+ }
+
+ if (run_count != 0)
+ rte_mbuf_raw_free_bulk(run_pool, &mbufs[run_start], run_count);
+}
+
/* Creates a shallow copy of mbuf */
RTE_EXPORT_SYMBOL(rte_pktmbuf_clone)
struct rte_mbuf *
--
2.51.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* RE: [PATCH v2] mbuf: avoid temporary array for bulk free
2026-09-29 0:25 ` [PATCH v2] " Nam Tran
@ 2026-09-29 4:39 ` Morten Brørup
2026-10-05 6:47 ` [EXTERNAL] " Ashwin Sekhar
2026-10-05 7:45 ` Konstantin Ananyev
2 siblings, 0 replies; 7+ messages in thread
From: Morten Brørup @ 2026-09-29 4:39 UTC (permalink / raw)
To: Nam Tran; +Cc: dev, stephen
> v2:
> - Cap same-pool runs at 64 to retain mempool-cache behavior.
NAK!
Why would you do that?
> - Move the sanity check after the chained-packet fallback decision.
> - Add mixed flat/chained and indirect-mbuf regression cases.
> - Remove the earlier timing claim; the capped version has not yet
> been benchmarked on x86 or with a cached mempool.
>
> + run_count++;
> + if (run_count == RTE_PKTMBUF_FREE_PENDING_SZ) {
> + rte_mbuf_raw_free_bulk(run_pool,
> + &mbufs[run_start], run_count);
> + run_count = 0;
> + }
NAK to this part.
Flushing a large bulk in smaller bulks of 64 mbufs is not necessary, and may reduce performance.
^ permalink raw reply [flat|nested] 7+ messages in thread
* RE: [EXTERNAL] [PATCH v2] mbuf: avoid temporary array for bulk free
2026-09-29 0:25 ` [PATCH v2] " Nam Tran
2026-09-29 4:39 ` Morten Brørup
@ 2026-10-05 6:47 ` Ashwin Sekhar
2026-10-05 7:45 ` Konstantin Ananyev
2 siblings, 0 replies; 7+ messages in thread
From: Ashwin Sekhar @ 2026-10-05 6:47 UTC (permalink / raw)
To: Nam Tran, mb@smartsharesystems.com
Cc: dev@dpdk.org, stephen@networkplumber.org
Acked by: Ashwin Sekhar T K <asekhar@marvell.com>
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2] mbuf: avoid temporary array for bulk free
2026-09-29 0:25 ` [PATCH v2] " Nam Tran
2026-09-29 4:39 ` Morten Brørup
2026-10-05 6:47 ` [EXTERNAL] " Ashwin Sekhar
@ 2026-10-05 7:45 ` Konstantin Ananyev
2 siblings, 0 replies; 7+ messages in thread
From: Konstantin Ananyev @ 2026-10-05 7:45 UTC (permalink / raw)
To: Nam Tran, mb; +Cc: dev, stephen
> rte_pktmbuf_free_bulk() currently stages freeable mbufs in a
> temporary array before returning them to their mempool. For flat
> packet arrays, this copies pointers even when the input array
> already contains contiguous freeable mbufs.
>
> Track contiguous same-pool runs in the input array and pass them
> directly to rte_mbuf_raw_free_bulk(). Flush at 64 entries to
> preserve the existing mempool-cache behavior. Also flush on NULL
> entries, retained mbufs, and pool changes. Keep the array-based
> fallback for chained packets.
>
> Add coverage for NULL entries, mixed mempools, shared and indirect
> mbufs, and a flat run followed by a chained packet.
>
> Signed-off-by: Nam Tran <hoangnamtran18122005@gmail.com>
> Reviewed-by: Morten Brørup <mb@smartsharesystems.com>
> ---
> v2:
> - Cap same-pool runs at 64 to retain mempool-cache behavior.
> - Move the sanity check after the chained-packet fallback decision.
> - Add mixed flat/chained and indirect-mbuf regression cases.
> - Remove the earlier timing claim; the capped version has not yet
> been benchmarked on x86 or with a cached mempool.
>
> Tested: ARM64 Linux debug build and mbuf_autotest passed.
>
> app/test/test_mbuf.c | 94 ++++++++++++++++++++++++++++++++++++++++++++
> lib/mbuf/rte_mbuf.c | 74 ++++++++++++++++++++++++++++++++--
> 2 files changed, 165 insertions(+), 3 deletions(-)
>
> diff --git a/app/test/test_mbuf.c b/app/test/test_mbuf.c
> index db23259745..249e541173 100644
> --- a/app/test/test_mbuf.c
> +++ b/app/test/test_mbuf.c
> @@ -833,6 +833,100 @@ test_pktmbuf_pool_bulk(void)
> goto err;
> }
>
> + printf("Test bulk free with NULL entries.\n");
> +
> + ret = rte_pktmbuf_alloc_bulk(pool, mbufs, 4);
> + if (ret != 0)
> + goto err;
> +
> + m = mbufs[1];
> + mbufs[1] = NULL;
> + rte_pktmbuf_free_bulk(mbufs, 4);
> + rte_pktmbuf_free(m);
> +
> + if (!rte_mempool_full(pool)) {
> + printf("mempool not full after NULL-entry bulk free\n");
> + goto err;
> + }
> +
> + printf("Test bulk free with multiple pools.\n");
> +
> + for (i = 0; i < 4; i++) {
> + mbufs[i] = rte_pktmbuf_alloc((i & 1) ? pool2 : pool);
> + if (mbufs[i] == NULL)
> + goto err;
> + }
> +
> + rte_pktmbuf_free_bulk(mbufs, 4);
> +
> + if (!(rte_mempool_full(pool) && rte_mempool_full(pool2))) {
> + printf("mempools not full after mixed-pool bulk free\n");
> + goto err;
> + }
> +
> + printf("Test bulk free with shared mbuf.\n");
> +
> + ret = rte_pktmbuf_alloc_bulk(pool, mbufs, 4);
> + if (ret != 0)
> + goto err;
> +
> + m = mbufs[1];
> + rte_mbuf_refcnt_update(m, 1);
> + rte_pktmbuf_free_bulk(mbufs, 4);
> +
> + if (rte_mbuf_refcnt_read(m) != 1) {
> + printf("shared mbuf reference count incorrect\n");
> + goto err;
> + }
> +
> + rte_pktmbuf_free(m);
> +
> + if (!rte_mempool_full(pool)) {
> + printf("mempool not full after shared mbuf free\n");
> + goto err;
> + }
> +
> + printf("Test bulk free of flat mbufs around a chain.\n");
> +
> + ret = rte_pktmbuf_alloc_bulk(pool, mbufs, 5);
> + if (ret != 0)
> + goto err;
> +
> + ret = rte_pktmbuf_chain(mbufs[1], mbufs[2]);
> + if (ret != 0)
> + goto err;
> + mbufs[2] = mbufs[3];
> + mbufs[3] = mbufs[4];
> + rte_pktmbuf_free_bulk(mbufs, 4);
> +
> + if (!rte_mempool_full(pool)) {
> + printf("mempool not full after mixed flat and chain bulk free\n");
> + goto err;
> + }
> +
> + printf("Test bulk free with an indirect mbuf.\n");
> +
> + m = rte_pktmbuf_alloc(pool);
> + if (m == NULL)
> + goto err;
> + mbufs[0] = rte_pktmbuf_alloc(pool);
> + mbufs[1] = rte_pktmbuf_clone(m, pool);
> + mbufs[2] = rte_pktmbuf_alloc(pool);
> + if (mbufs[0] == NULL || mbufs[1] == NULL || mbufs[2] == NULL)
> + goto err;
> +
> + rte_pktmbuf_free_bulk(mbufs, 3);
> + if (rte_mbuf_refcnt_read(m) != 1) {
> + printf("indirect mbuf reference count incorrect\n");
> + goto err;
> + }
> + rte_pktmbuf_free(m);
> +
> + if (!rte_mempool_full(pool)) {
> + printf("mempool not full after indirect mbuf bulk free\n");
> + goto err;
> + }
> +
> printf("Test bulk free of single long chain.\n");
>
> /* Bulk allocate all mbufs in the pool, in one go. */
> diff --git a/lib/mbuf/rte_mbuf.c b/lib/mbuf/rte_mbuf.c
> index 005bfaa573..99b2ea07cf 100644
> --- a/lib/mbuf/rte_mbuf.c
> +++ b/lib/mbuf/rte_mbuf.c
> @@ -555,9 +555,8 @@ __rte_pktmbuf_free_seg_via_array(struct rte_mbuf *m,
> */
> #define RTE_PKTMBUF_FREE_PENDING_SZ 64
>
> -/* Free a bulk of packet mbufs back into their original mempools. */
> -RTE_EXPORT_SYMBOL(rte_pktmbuf_free_bulk)
> -void rte_pktmbuf_free_bulk(struct rte_mbuf **mbufs, unsigned int count)
> +static void
> +__rte_pktmbuf_free_bulk_fallback(struct rte_mbuf **mbufs, unsigned int count)
> {
> struct rte_mbuf *m, *m_next, *pending[RTE_PKTMBUF_FREE_PENDING_SZ];
> unsigned int idx, nb_pending = 0;
> @@ -582,6 +581,75 @@ void rte_pktmbuf_free_bulk(struct rte_mbuf **mbufs, unsigned int count)
> rte_mbuf_raw_free_bulk(pending[0]->pool, pending, nb_pending);
> }
>
> +/* Free a bulk of packet mbufs back into their original mempools. */
> +RTE_EXPORT_SYMBOL(rte_pktmbuf_free_bulk)
> +void rte_pktmbuf_free_bulk(struct rte_mbuf **mbufs, unsigned int count)
> +{
> + struct rte_mempool *run_pool = NULL;
> + unsigned int run_start = 0;
> + unsigned int run_count = 0;
> + unsigned int idx;
> +
> + for (idx = 0; idx < count; idx++) {
> + struct rte_mbuf *m = mbufs[idx];
> +
> + if (unlikely(m == NULL)) {
> + if (run_count != 0) {
> + rte_mbuf_raw_free_bulk(run_pool,
> + &mbufs[run_start], run_count);
> + run_count = 0;
here (and in other similar places), I think you also has to update
run_start also.
BTW, I don't think you need both run_count and run_start.
Knowing the run_count and idx is enough to deduce run_start (or
visa-versa).
> + }
> + continue;
> + }
> +
> + /*
> + * Preserve the generic path for chained packets. No mbuf in
> + * this suffix has been modified yet.
> + */
> + if (unlikely(m->next != NULL)) {
> + if (run_count != 0)
> + rte_mbuf_raw_free_bulk(run_pool,
> + &mbufs[run_start], run_count);
> +
> + __rte_pktmbuf_free_bulk_fallback(&mbufs[idx],
> + count - idx);
> + return;
> + }
> +
> + __rte_mbuf_sanity_check(m, 1);
> + m = rte_pktmbuf_prefree_seg(m);
> + if (unlikely(m == NULL)) {
> + if (run_count != 0) {
> + rte_mbuf_raw_free_bulk(run_pool,
> + &mbufs[run_start], run_count);
> + run_count = 0;
> + }
> + continue;
> + }
> +
> + if (run_count != 0 && m->pool != run_pool) {
> + rte_mbuf_raw_free_bulk(run_pool,
> + &mbufs[run_start], run_count);
> + run_count = 0;
> + }
> +
> + if (run_count == 0) {
> + run_pool = m->pool;
> + run_start = idx;
> + }
> +
> + run_count++;
> + if (run_count == RTE_PKTMBUF_FREE_PENDING_SZ) {
> + rte_mbuf_raw_free_bulk(run_pool,
> + &mbufs[run_start], run_count);
> + run_count = 0;
> + }
> + }
> +
> + if (run_count != 0)
> + rte_mbuf_raw_free_bulk(run_pool, &mbufs[run_start], run_count);
> +}
> +
> /* Creates a shallow copy of mbuf */
> RTE_EXPORT_SYMBOL(rte_pktmbuf_clone)
> struct rte_mbuf *
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-05 7:45 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-22 1:28 [PATCH] mbuf: avoid temporary array for bulk free Nam Tran
2026-09-22 10:59 ` Morten Brørup
2026-09-22 13:54 ` Stephen Hemminger
2026-09-29 0:25 ` [PATCH v2] " Nam Tran
2026-09-29 4:39 ` Morten Brørup
2026-10-05 6:47 ` [EXTERNAL] " Ashwin Sekhar
2026-10-05 7:45 ` Konstantin Ananyev
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox