> From: Nam Tran [mailto:[email protected]] > 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 <[email protected]>
Good idea. With the changes described inline below, Reviewed-by: Morten Brørup <[email protected]> > --- > 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

