[PATCH] mbuf: avoid temporary array for bulk free
Morten Brørup
mb at smartsharesystems.com
Tue Sep 22 12:59:04 CEST 2026
> From: Nam Tran [mailto:hoangnamtran18122005 at 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 at gmail.com>
Good idea.
With the changes described inline below,
Reviewed-by: Morten Brørup <mb at 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
More information about the dev
mailing list