> From: Morten Brørup [mailto:[email protected]]
> Sent: Wednesday, 12 August 2026 11.07
> 
> This patch introduces some mempool optimizations, which might be
> controvesial.
> 
> 1. Access local cache without first accessing the mempool header
> struct.
> 
> When getting/putting objects in a mempool cache, it required accessing
> the "size" field and "local_cache" pointer in the mempool structure, to
> determine if the local cache was present.
> 
> The mempool structure was changed, so the local cache array is now an
> integral part of the mempool structure.
> This means that local cache can be accessed directly, without first
> checking the "size" and "local_cache" fields in the mempool header
> structure.
> This avoids a couple of load operations with a potential CPU cache miss
> when the mempool header itself is not hot in the CPU cache.
> The "local_cache" field was changed from being a pointer to the local
> cache array, and instead became the local cache array itself.
> 
> This change similarly speeds up rte_mempool_get_priv(), because it no
> longer needs to access the mempool header structure (specifically, the
> "cache_size" field) to determine the address of the mempool's private
> data.
> 
> Disadvantage: Memory for local cache is also consumed by mempools
> configured without cache.

Considering the patch rightsizing the local cache [1], we should not pursue 
this first optimization.

[1]: 
https://patchwork.dpdk.org/project/dpdk/patch/[email protected]/

> 
> Related changes:
> - The mempool cache audit function was improved.
> - The mempool autotest accessed the internal RTE_MEMPOOL_HEADER_SIZE
>   macro, and was updated accordingly.
> 
> 2. Move objects in mempool cache as 32-byte chunks at CPU cache line
>    aligned addresses.
> 
> Improved memory copy performance by ensuring that objects in mempool
> cache
> can be moved as 32-byte chunks at CPU cache line aligned addresses.
> 
> Disadvantage: Mempool cache size must be divisible by 32.
> For compatibility purposes, a requested cache size not divisible by 32
> is handled by a graceful fallback at mempool cache creation and
> mempool creation.

I got no feedback on this second optimization, and it is somewhat intrusive, so 
I will mark it as Rejected (for now).

> 
> Related changes:
> - The TAP driver used a mempool cache size of 4 mbufs for GSO,
>   and was updated to a cache size of 32 mbufs.
> 
> Other changes:
> - The description of the RTE_MEMPOOL_NAMESIZE macro was expanded to
>   explain how the value is derived.
> 
> Signed-off-by: Morten Brørup <[email protected]>
> ---
> Depends-on: patch-167311 ("[v3] mempool: remove cache flush threshold
> field")
> ---
>  app/test/test_mempool.c       |  3 +-
>  drivers/net/tap/rte_eth_tap.c |  2 +-
>  lib/eal/include/rte_common.h  | 12 ++++++
>  lib/mempool/rte_mempool.c     | 68 ++++++++++++++++++++++-------
>  lib/mempool/rte_mempool.h     | 81 ++++++++++++++++++++++-------------
>  5 files changed, 117 insertions(+), 49 deletions(-)
> 
> diff --git a/app/test/test_mempool.c b/app/test/test_mempool.c
> index e54249ce61..76d45cea2a 100644
> --- a/app/test/test_mempool.c
> +++ b/app/test/test_mempool.c
> @@ -112,8 +112,7 @@ test_mempool_basic(struct rte_mempool *mp, int
> use_external_cache)
>               GOTO_ERR(ret, out);
> 
>       printf("get private data\n");
> -     if (rte_mempool_get_priv(mp) != (char *)mp +
> -                     RTE_MEMPOOL_HEADER_SIZE(mp, mp->cache_size))
> +     if (rte_mempool_get_priv(mp) != (char *)mp + sizeof(struct
> rte_mempool))
>               GOTO_ERR(ret, out);
> 
>  #ifndef RTE_EXEC_ENV_FREEBSD /* rte_mem_virt2iova() not supported on
> bsd */
> diff --git a/drivers/net/tap/rte_eth_tap.c
> b/drivers/net/tap/rte_eth_tap.c
> index b93452f168..b3142561c2 100644
> --- a/drivers/net/tap/rte_eth_tap.c
> +++ b/drivers/net/tap/rte_eth_tap.c
> @@ -61,7 +61,7 @@
>  #define TAP_MAX_MAC_ADDRS    16
>  #define TAP_GSO_MBUFS_PER_CORE       128
>  #define TAP_GSO_MBUF_SEG_SIZE        128
> -#define TAP_GSO_MBUF_CACHE_SIZE      4
> +#define TAP_GSO_MBUF_CACHE_SIZE      32
>  #define TAP_GSO_MBUFS_NUM \
>       (TAP_GSO_MBUFS_PER_CORE * TAP_GSO_MBUF_CACHE_SIZE)
> 
> diff --git a/lib/eal/include/rte_common.h
> b/lib/eal/include/rte_common.h
> index 79d2a0ab93..0fd0906506 100644
> --- a/lib/eal/include/rte_common.h
> +++ b/lib/eal/include/rte_common.h
> @@ -567,6 +567,15 @@ static void
> __attribute__((destructor(RTE_PRIO(prio)), used)) func(void)
>  #define __rte_assume(condition) __assume(condition)
>  #endif
> 
> +/**
> + * Alignment hint precondition
> + */
> +#ifdef RTE_TOOLCHAIN_MSVC
> +#define __rte_assume_aligned(ptr, alignment) (ptr)
> +#else
> +#define __rte_assume_aligned(ptr, alignment)
> __builtin_assume_aligned(ptr, alignment)
> +#endif
> +
>  /**
>   * Disable AddressSanitizer on some code
>   */
> @@ -775,6 +784,9 @@ rte_is_aligned(const void * const __rte_restrict
> ptr, const unsigned int align)
>  /** Force minimum cache line alignment. */
>  #define __rte_cache_min_aligned __rte_aligned(RTE_CACHE_LINE_MIN_SIZE)
> 
> +/** Cache alignment hint precondition */
> +#define __rte_assume_cache_aligned(ptr) __rte_assume_aligned(ptr,
> RTE_CACHE_LINE_SIZE)
> +
>  #define _RTE_CACHE_GUARD_HELPER2(unique) \
>       alignas(RTE_CACHE_LINE_SIZE) \
>       char cache_guard_ ## unique[RTE_CACHE_LINE_SIZE *
> RTE_CACHE_GUARD_LINES]
> diff --git a/lib/mempool/rte_mempool.c b/lib/mempool/rte_mempool.c
> index 09660e89ac..5d43c7c98b 100644
> --- a/lib/mempool/rte_mempool.c
> +++ b/lib/mempool/rte_mempool.c
> @@ -759,7 +759,7 @@ mempool_cache_init(struct rte_mempool_cache *cache,
> uint32_t size)
>  /*
>   * Create and initialize a cache for objects that are retrieved from
> and
>   * returned to an underlying mempool. This structure is identical to
> the
> - * local_cache[lcore_id] pointed to by the mempool structure.
> + * local_cache[lcore_id] entry in the mempool structure.
>   */
>  RTE_EXPORT_SYMBOL(rte_mempool_cache_create)
>  struct rte_mempool_cache *
> @@ -767,6 +767,23 @@ rte_mempool_cache_create(uint32_t size, int
> socket_id)
>  {
>       struct rte_mempool_cache *cache;
> 
> +     /*
> +      * Alignment requirement for performance optimized move within
> the mempool cache.
> +      * @ref rte_mempool_do_generic_put() implementation.
> +      */
> +     if (size < 32) {
> +             RTE_MEMPOOL_LOG(WARNING,
> +                             "Tiny cache size %u not divisible by 32,
> rounding up to 32.",
> +                             size);
> +             size = 32;
> +     } else if (size & 31) {
> +             uint32_t rounded = RTE_ALIGN_MUL_FLOOR(size, 32);
> +             RTE_MEMPOOL_LOG(INFO,
> +                             "Cache size %u not divisible by 32, rounding
> down to %u.",
> +                             size, rounded);
> +             size = rounded;
> +     }
> +
>       if (size == 0 || size > RTE_MEMPOOL_CACHE_MAX_SIZE) {
>               rte_errno = EINVAL;
>               return NULL;
> @@ -837,6 +854,26 @@ rte_mempool_create_empty(const char *name,
> unsigned n, unsigned elt_size,
>               return NULL;
>       }
> 
> +     /*
> +      * Alignment requirement for performance optimized move within
> the mempool cache.
> +      * @ref rte_mempool_do_generic_put() implementation.
> +      */
> +     RTE_BUILD_BUG_ON(((sizeof(void *) * RTE_MEMPOOL_CACHE_MAX_SIZE /
> 2) &
> +                     RTE_CACHE_LINE_MASK) != 0);
> +     RTE_BUILD_BUG_ON((RTE_MEMPOOL_CACHE_MAX_SIZE & 31) != 0);
> +     if (cache_size & 31) {
> +             unsigned int rounded = RTE_ALIGN_MUL_FLOOR(cache_size, 32);
> +             if (rounded == 0)
> +                     RTE_MEMPOOL_LOG(WARNING,
> +                                     "Tiny cache size %u not divisible by 32,
> disabling cache.",
> +                                     cache_size);
> +             else
> +                     RTE_MEMPOOL_LOG(INFO,
> +                                     "Cache size %u not divisible by 32,
> rounding down to %u.",
> +                                     cache_size, rounded);
> +             cache_size = rounded;
> +     }
> +
>       /* asked cache too big */
>       if (cache_size > RTE_MEMPOOL_CACHE_MAX_SIZE ||
>           cache_size > n) {
> @@ -883,7 +920,7 @@ rte_mempool_create_empty(const char *name, unsigned
> n, unsigned elt_size,
>               goto exit_unlock;
>       }
> 
> -     mempool_size = RTE_MEMPOOL_HEADER_SIZE(mp, cache_size);
> +     mempool_size = sizeof(struct rte_mempool);
>       mempool_size += private_data_size;
>       mempool_size = RTE_ALIGN_CEIL(mempool_size, RTE_MEMPOOL_ALIGN);
> 
> @@ -899,7 +936,7 @@ rte_mempool_create_empty(const char *name, unsigned
> n, unsigned elt_size,
> 
>       /* init the mempool structure */
>       mp = mz->addr;
> -     memset(mp, 0, RTE_MEMPOOL_HEADER_SIZE(mp, cache_size));
> +     memset(mp, 0, mempool_size);
>       ret = strlcpy(mp->name, name, sizeof(mp->name));
>       if (ret < 0 || ret >= (int)sizeof(mp->name)) {
>               rte_errno = ENAMETOOLONG;
> @@ -936,13 +973,6 @@ rte_mempool_create_empty(const char *name,
> unsigned n, unsigned elt_size,
>               goto exit_unlock;
>       }
> 
> -     /*
> -      * local_cache pointer is set even if cache_size is zero.
> -      * The local_cache points to just past the elt_pa[] array.
> -      */
> -     mp->local_cache = (struct rte_mempool_cache *)
> -             RTE_PTR_ADD(mp, RTE_MEMPOOL_HEADER_SIZE(mp, 0));
> -
>       /* Init all default caches. */
>       if (cache_size != 0) {
>               for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++)
> @@ -1218,17 +1248,23 @@ static void
>  mempool_audit_cache(const struct rte_mempool *mp)
>  {
>       unsigned lcore_id;
> +     const uint32_t cache_size = mp->cache_size;
> 
> -     if (mp->cache_size == 0)
> -             return;
> +     if (cache_size > RTE_MEMPOOL_CACHE_MAX_SIZE) {
> +             RTE_MEMPOOL_LOG(CRIT, "badness on cache size");
> +             rte_panic("MEMPOOL: invalid cache size\n");
> +     }
> 
>       for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {
>               const struct rte_mempool_cache *cache;
>               cache = &mp->local_cache[lcore_id];
> -             if (cache->len > RTE_DIM(cache->objs)) {
> -                     RTE_MEMPOOL_LOG(CRIT, "badness on cache[%u]",
> -                             lcore_id);
> -                     rte_panic("MEMPOOL: invalid cache len\n");
> +             if (cache->size != cache_size) {
> +                     RTE_MEMPOOL_LOG(CRIT, "badness on cache[%u] size",
> lcore_id);
> +                     rte_panic("MEMPOOL: invalid cache[%u] size\n",
> lcore_id);
> +             }
> +             if (cache->len > cache_size) {
> +                     RTE_MEMPOOL_LOG(CRIT, "badness on cache[%u] len",
> lcore_id);
> +                     rte_panic("MEMPOOL: invalid cache[%u] len\n",
> lcore_id);
>               }
>       }
>  }
> diff --git a/lib/mempool/rte_mempool.h b/lib/mempool/rte_mempool.h
> index 2fa70812d5..a5578b3a93 100644
> --- a/lib/mempool/rte_mempool.h
> +++ b/lib/mempool/rte_mempool.h
> @@ -120,9 +120,26 @@ struct rte_mempool_objsz {
>       /**< Total size of an object (header + elt + trailer). */
>  };
> 
> -/**< Maximum length of a memory pool's name. */
> -#define RTE_MEMPOOL_NAMESIZE (RTE_RING_NAMESIZE - \
> -                           sizeof(RTE_MEMPOOL_MZ_PREFIX) + 1)
> +/* Represents the memzone prefix of the default mempool driver. */
> +#define RTE_MEMPOOL_DRIVER_REPRESENTATIVE_MZ_PREFIX "RG_"
> +
> +/**
> + * Maximum length of a memory pool's name.
> + *
> + * Needs room for memzone prefix indicating "mempool" type:
> + * "MP_<name>"
> + * Note:
> + * The mempool driver needs room for its own memzone prefix too, e.g.:
> + * "RG_MP_<name>" (ring driver) or "STK_MP_<name>" (stack driver)
> + * In order to fail early on too long names when creating the mempool,
> + * the length of the memzone name reserved by various mempool drivers
> is
> + * not considered; only the default driver (ring) is considered here.
> + * If the name eventually turns out to be too long for the chosen
> mempool driver,
> + * populating the mempool will fail.
> + */
> +#define RTE_MEMPOOL_NAMESIZE (RTE_MEMZONE_NAMESIZE - \
> +             (sizeof(RTE_MEMPOOL_DRIVER_REPRESENTATIVE_MZ_PREFIX) - 1) -
> \
> +             (sizeof(RTE_MEMPOOL_MZ_PREFIX) - 1))
>  #define RTE_MEMPOOL_MZ_PREFIX "MP_"
> 
>  /* "MP_<name>" */
> @@ -234,8 +251,7 @@ struct __rte_cache_aligned rte_mempool {
>       unsigned int flags;              /**< Flags of the mempool. */
>       int socket_id;                   /**< Socket id passed at create.
> */
>       uint32_t size;                   /**< Max size of the mempool. */
> -     uint32_t cache_size;
> -     /**< Size of per-lcore default local cache. */
> +     uint32_t cache_size;             /**< Size of per-lcore default
> local cache. */
> 
>       uint32_t elt_size;               /**< Size of an element. */
>       uint32_t header_size;            /**< Size of header (before
> elt). */
> @@ -251,13 +267,13 @@ struct __rte_cache_aligned rte_mempool {
>        */
>       int32_t ops_index;
> 
> -     struct rte_mempool_cache *local_cache; /**< Per-lcore local cache
> */
> -
>       uint32_t populated_size;         /**< Number of populated
> objects. */
>       struct rte_mempool_objhdr_list elt_list; /**< List of objects in
> pool */
>       uint32_t nb_mem_chunks;          /**< Number of memory chunks */
>       struct rte_mempool_memhdr_list mem_list; /**< List of memory
> chunks */
> 
> +     struct rte_mempool_cache local_cache[RTE_MAX_LCORE]; /**< Per-
> lcore local cache */
> +
>  #ifdef RTE_LIBRTE_MEMPOOL_STATS
>       /** Per-lcore statistics.
>        *
> @@ -265,6 +281,8 @@ struct __rte_cache_aligned rte_mempool {
>        */
>       struct rte_mempool_debug_stats stats[RTE_MAX_LCORE + 1];
>  #endif
> +
> +     /* Private data are located immediately after the mempool
> structure. */
>  };
> 
>  /** Spreading among memory channels not required. */
> @@ -356,18 +374,6 @@ struct __rte_cache_aligned rte_mempool {
>  #define RTE_MEMPOOL_CACHE_STAT_ADD(cache, name, n) do {} while (0)
>  #endif
> 
> -/**
> - * @internal Calculate the size of the mempool header.
> - *
> - * @param mp
> - *   Pointer to the memory pool.
> - * @param cs
> - *   Size of the per-lcore cache.
> - */
> -#define RTE_MEMPOOL_HEADER_SIZE(mp, cs) \
> -     (sizeof(*(mp)) + (((cs) == 0) ? 0 : \
> -     (sizeof(struct rte_mempool_cache) * RTE_MAX_LCORE)))
> -
>  /* return the header of a mempool object (internal) */
>  static inline struct rte_mempool_objhdr *
>  rte_mempool_get_header(void *obj)
> @@ -1043,7 +1049,7 @@ rte_mempool_free(struct rte_mempool *mp);
>   *   If cache_size is non-zero, the rte_mempool library will try to
>   *   limit the accesses to the common lockless pool, by maintaining a
>   *   per-lcore object cache. This argument must be lower or equal to
> - *   RTE_MEMPOOL_CACHE_MAX_SIZE and n.
> + *   RTE_MEMPOOL_CACHE_MAX_SIZE and n, and it must be divisible by 32.
>   *   The access to the per-lcore table is of course
>   *   faster than the multi-producer/consumer pool. The cache can be
>   *   disabled if the cache_size argument is set to 0; it can be useful
> to
> @@ -1362,15 +1368,16 @@ rte_mempool_cache_free(struct rte_mempool_cache
> *cache);
>  static __rte_always_inline struct rte_mempool_cache *
>  rte_mempool_default_cache(struct rte_mempool *mp, unsigned lcore_id)
>  {
> -     if (unlikely(mp->cache_size == 0))
> +     if (unlikely(lcore_id == LCORE_ID_ANY))
>               return NULL;
> 
> -     if (unlikely(lcore_id == LCORE_ID_ANY))
> +     struct rte_mempool_cache *cache = &mp->local_cache[lcore_id];
> +
> +     if (unlikely(cache->size == 0))
>               return NULL;
> 
> -     rte_mempool_trace_default_cache(mp, lcore_id,
> -             &mp->local_cache[lcore_id]);
> -     return &mp->local_cache[lcore_id];
> +     rte_mempool_trace_default_cache(mp, lcore_id, cache);
> +     return cache;
>  }
> 
>  /**
> @@ -1439,9 +1446,24 @@ rte_mempool_do_generic_put(struct rte_mempool
> *mp, void * const *obj_table,
>                * are more hot, from the upper half of the cache.
>                */
>               __rte_assume(cache->len > cache->size / 2);
> -             rte_mempool_ops_enqueue_bulk(mp, &cache->objs[0], cache-
> >size / 2);
> -             rte_memcpy(&cache->objs[0], &cache->objs[cache->size / 2],
> -                             sizeof(void *) * (cache->len - cache->size /
> 2));
> +             rte_mempool_ops_enqueue_bulk(mp, cache->objs, cache->size /
> 2);
> +             /*
> +              * For improved rte_memcpy() performance, move down objects
> +              * from CPU cache line aligned address in chunks of 32
> bytes.
> +              * Note: For cache->objs[cache->size / 2] to be cache line
> aligned, cache->size
> +              * must be divisible by 32 on 32-bit architecture with 64-
> byte cache line,
> +              * divisible by 32 on 64-bit architecture with 128-byte
> cache line, and
> +              * be divisible by 16 on 64-bit architecture with 64-byte
> cache line.
> +              * For API consistency, require mempool cache size is
> divisible by 32.
> +              * This requirement is enforced when creating the cache.
> +              * @ref rte_mempool_create_empty() implementation.
> +              */
> +             const size_t move = RTE_ALIGN_MUL_CEIL(
> +                             sizeof(void *) * (cache->len - cache->size /
> 2), 32);
> +             __rte_assume(move >= 32);
> +             __rte_assume((move & 31) == 0);
> +             rte_memcpy(cache->objs, __rte_assume_cache_aligned(&cache-
> >objs[cache->size / 2]),
> +                             move);
>               cache_objs = &cache->objs[cache->len - cache->size / 2];
>               cache->len = cache->len - cache->size / 2 + n;
>       } else {
> @@ -1886,8 +1908,7 @@ void rte_mempool_audit(struct rte_mempool *mp);
>   */
>  static inline void *rte_mempool_get_priv(struct rte_mempool *mp)
>  {
> -     return (char *)mp +
> -             RTE_MEMPOOL_HEADER_SIZE(mp, mp->cache_size);
> +     return (char *)mp + sizeof(struct rte_mempool);
>  }
> 
>  /**
> --
> 2.43.0

Reply via email to