[RFC] lib/st_ring: add single thread ring

Message ID 20230821060420.3509667-1-honnappa.nagarahalli@arm.com (mailing list archive)
State New
Delegated to: Thomas Monjalon
Headers
Series [RFC] lib/st_ring: add single thread ring |

Checks

Context Check Description
ci/checkpatch warning coding style issues
ci/loongarch-compilation success Compilation OK
ci/loongarch-unit-testing success Unit Testing PASS
ci/Intel-compilation success Compilation OK
ci/intel-Testing success Testing PASS
ci/intel-Functional success Functional PASS

Commit Message

Honnappa Nagarahalli Aug. 21, 2023, 6:04 a.m. UTC
  Add a single thread safe and multi-thread unsafe ring data structure.
This library provides an simple and efficient alternative to multi-thread
safe ring when multi-thread safety is not required.

Signed-off-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
---
v1:
1) The code is very prelimnary and is not even compiled
2) This is intended to show the APIs and some thoughts on implementation
3) More APIs and the rest of the implementation will come in subsequent
   versions

 lib/st_ring/rte_st_ring.h | 567 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 567 insertions(+)
 create mode 100644 lib/st_ring/rte_st_ring.h
  

Comments

Morten Brørup Aug. 21, 2023, 7:37 a.m. UTC | #1
> From: Honnappa Nagarahalli [mailto:honnappa.nagarahalli@arm.com]
> Sent: Monday, 21 August 2023 08.04
> 
> Add a single thread safe and multi-thread unsafe ring data structure.
> This library provides an simple and efficient alternative to multi-
> thread
> safe ring when multi-thread safety is not required.
> 
> Signed-off-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
> ---

Good idea.

However, I prefer it to be implemented in the ring lib as one more ring type. That would also give us a lot of the infrastructure (management functions, documentation and tests) for free.

The ring lib already has performance-optimized APIs for single-consumer and single-producer use, rte_ring_sc_dequeue_bulk() and rte_ring_sp_enqueue_burst(). Similar performance-optimized APIs for single-thread use could be added: rte_ring_st_dequeue_bulk() and rte_ring_st_enqueue_burst().

Regardless if added to the ring lib or as a separate lib, "reverse" APIs (for single-thread use only) and zero-copy APIs can be added at any time later.
  
Mattias Rönnblom Aug. 21, 2023, 9:14 p.m. UTC | #2
On 2023-08-21 08:04, Honnappa Nagarahalli wrote:
> Add a single thread safe and multi-thread unsafe ring data structure.

One must have set the bar very low, if one needs to specify that an API 
is single-thread safe.

> This library provides an simple and efficient alternative to multi-thread
> safe ring when multi-thread safety is not required.
> 
> Signed-off-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
> ---
> v1:
> 1) The code is very prelimnary and is not even compiled
> 2) This is intended to show the APIs and some thoughts on implementation

If you haven't done it already, maybe it might be worth looking around 
in the code base for already-existing, more-or-less open-coded 
fifo/circular buffer type data structures. Just to make sure those can 
be eliminated if this makes it into DPDK.

There's one in rte_event_eth_rx_adapter.c, and I think one in the SW 
eventdev as well. Seems to be one in cmdline_cirbuf.h as well. I'm sure 
there are many more.

You could pick some other name for it, instead of the slightly awkward 
"st_ring" (e.g., "fifo", "cbuf", "cbuffer", "circ_buffer"). That would 
also leave you with more freedom to stray from the MT safe ring API 
without surprising the user, if needed (and I think it is needed).

Hopefully you can reduce API complexity compared to the MT-safe version. 
Having a name for these kinds of data structures doesn't make a lot of 
sense, for example. Skip the dump function. Relax from always_inline to 
just regular inline.

I'm not sure you need bulk/burst type operations. Without any memory 
fences, an optimizing compiler should do a pretty good job of unrolling 
multiple-element access type operations, assuming you leave the ST ring 
code in the header files (otherwise LTO is needed).

I think you will want a peek-type operation on the reader side. That 
more for convenience, rather than that I think the copies will actually 
be there in the object code (such should be eliminated by the compiler, 
given that the barriers are gone).

> 3) More APIs and the rest of the implementation will come in subsequent
>     versions
> 
>   lib/st_ring/rte_st_ring.h | 567 ++++++++++++++++++++++++++++++++++++++
>   1 file changed, 567 insertions(+)
>   create mode 100644 lib/st_ring/rte_st_ring.h
> 
> diff --git a/lib/st_ring/rte_st_ring.h b/lib/st_ring/rte_st_ring.h
> new file mode 100644
> index 0000000000..8cb8832591
> --- /dev/null
> +++ b/lib/st_ring/rte_st_ring.h
> @@ -0,0 +1,567 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Arm Limited
> + */
> +
> +#ifndef _RTE_ST_RING_H_
> +#define _RTE_ST_RING_H_
> +
> +/**
> + * @file
> + * RTE Signle Thread Ring (ST Ring)
> + *
> + * The ST Ring is a fixed-size queue intended to be accessed
> + * by one thread at a time. It does not provide concurrent access to
> + * multiple threads. If there are multiple threads accessing the ST ring,
> + * then the threads have to use locks to protect the ring from
> + * getting corrupted.

You are basically saying the same thing three times here.

> + *
> + * - FIFO (First In First Out)
> + * - Maximum size is fixed; the pointers are stored in a table.
> + * - Consumer and producer part of same thread.
> + * - Multi-thread producers and consumers need locking.

...two more times here. One might get the impression you really don't 
trust the reader.

> + * - Single/Bulk/burst dequeue at Tail or Head
> + * - Single/Bulk/burst enqueue at Head or Tail

Does this not sound more like a deque, than a FIFO/circular buffer? Are 
there any examples where this functionality (the double-endedness) is 
needed in the DPDK code base?

> + *
> + */
> +
> +#ifdef __cplusplus
> +extern "C" {
> +#endif
> +
> +#include <rte_st_ring_core.h>
> +#include <rte_st_ring_elem.h>

Is the intention to provide a ring with compile-time variable element 
size? In other words, where the elements of a particular ring instance 
has the same element size, but different rings may have different 
element sizes.

Seems like a good idea to me, in that case. Although often you will have 
pointers, it would be useful to store larger things like small structs, 
and maybe smaller elements as well.

> +
> +/**
> + * Calculate the memory size needed for a ST ring
> + *
> + * This function returns the number of bytes needed for a ST ring, given
> + * the number of elements in it. This value is the sum of the size of
> + * the structure rte_st_ring and the size of the memory needed by the
> + * elements. The value is aligned to a cache line size.
> + *
> + * @param count
> + *   The number of elements in the ring (must be a power of 2).
> + * @return
> + *   - The memory size needed for the ST ring on success.
> + *   - -EINVAL if count is not a power of 2.
> + */
> +ssize_t rte_st_ring_get_memsize(unsigned int count);
> +
> +/**
> + * Initialize a ST ring structure.
> + *
> + * Initialize a ST ring structure in memory pointed by "r". The size of the
> + * memory area must be large enough to store the ring structure and the
> + * object table. It is advised to use rte_st_ring_get_memsize() to get the
> + * appropriate size.
> + *
> + * The ST ring size is set to *count*, which must be a power of two.
> + * The real usable ring size is *count-1* instead of *count* to
> + * differentiate a full ring from an empty ring.
> + *
> + * The ring is not added in RTE_TAILQ_ST_RING global list. Indeed, the
> + * memory given by the caller may not be shareable among dpdk
> + * processes.
> + *
> + * @param r
> + *   The pointer to the ring structure followed by the elements table.
> + * @param name
> + *   The name of the ring.
> + * @param count
> + *   The number of elements in the ring (must be a power of 2,
> + *   unless RTE_ST_RING_F_EXACT_SZ is set in flags).
> + * @param flags
> + *   An OR of the following:
> + *   - RTE_ST_RING_F_EXACT_SZ: If this flag is set, the ring will hold
> + *     exactly the requested number of entries, and the requested size
> + *     will be rounded up to the next power of two, but the usable space
> + *     will be exactly that requested. Worst case, if a power-of-2 size is
> + *     requested, half the ring space will be wasted.
> + *     Without this flag set, the ring size requested must be a power of 2,
> + *     and the usable space will be that size - 1.
> + * @return
> + *   0 on success, or a negative value on error.
> + */
> +int rte_st_ring_init(struct rte_st_ring *r, const char *name,
> +	unsigned int count, unsigned int flags);
> +
> +/**
> + * Create a new ST ring named *name* in memory.
> + *
> + * This function uses ``memzone_reserve()`` to allocate memory. Then it
> + * calls rte_st_ring_init() to initialize an empty ring.
> + *
> + * The new ring size is set to *count*, which must be a power of two.
> + * The real usable ring size is *count-1* instead of *count* to
> + * differentiate a full ring from an empty ring.
> + *
> + * The ring is added in RTE_TAILQ_ST_RING list.
> + *
> + * @param name
> + *   The name of the ring.
> + * @param count
> + *   The size of the ring (must be a power of 2,
> + *   unless RTE_ST_RING_F_EXACT_SZ is set in flags).
> + * @param socket_id
> + *   The *socket_id* argument is the socket identifier in case of
> + *   NUMA. The value can be *SOCKET_ID_ANY* if there is no NUMA
> + *   constraint for the reserved zone.
> + * @param flags
> + *   - RTE_ST_RING_F_EXACT_SZ: If this flag is set, the ring will hold exactly the
> + *     requested number of entries, and the requested size will be rounded up
> + *     to the next power of two, but the usable space will be exactly that
> + *     requested. Worst case, if a power-of-2 size is requested, half the
> + *     ring space will be wasted.
> + *     Without this flag set, the ring size requested must be a power of 2,
> + *     and the usable space will be that size - 1.
> + * @return
> + *   On success, the pointer to the new allocated ring. NULL on error with
> + *    rte_errno set appropriately. Possible errno values include:
> + *    - E_RTE_NO_CONFIG - function could not get pointer to rte_config structure
> + *    - EINVAL - count provided is not a power of 2
> + *    - ENOSPC - the maximum number of memzones has already been allocated
> + *    - EEXIST - a memzone with the same name already exists
> + *    - ENOMEM - no appropriate memory area found in which to create memzone
> + */
> +struct rte_st_ring *rte_st_ring_create(const char *name, unsigned int count,
> +				 int socket_id, unsigned int flags);
> +
> +/**
> + * De-allocate all memory used by the ring.
> + *
> + * @param r
> + *   Ring to free.
> + *   If NULL then, the function does nothing.
> + */
> +void rte_st_ring_free(struct rte_st_ring *r);
> +
> +/**
> + * Dump the status of the ring to a file.
> + *
> + * @param f
> + *   A pointer to a file for output
> + * @param r
> + *   A pointer to the ring structure.
> + */
> +void rte_st_ring_dump(FILE *f, const struct rte_st_ring *r);
> +
> +/**
> + * Enqueue fixed number of objects on a ST ring.
> + *
> + * This function copies the objects at the head of the ring and
> + * moves the head index.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_table
> + *   A pointer to a table of void * pointers (objects).
> + * @param n
> + *   The number of objects to add in the ring from the obj_table.
> + * @param free_space
> + *   if non-NULL, returns the amount of space in the ring after the
> + *   enqueue operation has finished.
> + * @return
> + *   The number of objects enqueued, either 0 or n
> + */
> +static __rte_always_inline unsigned int
> +rte_st_ring_enqueue_bulk(struct rte_st_ring *r, void * const *obj_table,
> +		      unsigned int n, unsigned int *free_space)
> +{
> +	return rte_st_ring_enqueue_bulk_elem(r, obj_table, sizeof(void *),
> +			n, free_space);
> +}
> +
> +/**
> + * Enqueue upto a maximum number of objects on a ST ring.
> + *
> + * This function copies the objects at the head of the ring and
> + * moves the head index.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_table
> + *   A pointer to a table of void * pointers (objects).
> + * @param n
> + *   The number of objects to add in the ring from the obj_table.
> + * @param free_space
> + *   if non-NULL, returns the amount of space in the ring after the
> + *   enqueue operation has finished.
> + * @return
> + *   - n: Actual number of objects enqueued.
> + */
> +static __rte_always_inline unsigned int
> +rte_st_ring_enqueue_burst(struct rte_st_ring *r, void * const *obj_table,
> +		      unsigned int n, unsigned int *free_space)
> +{
> +	return rte_st_ring_enqueue_burst_elem(r, obj_table, sizeof(void *),
> +			n, free_space);
> +}
> +
> +/**
> + * Enqueue one object on a ST ring.
> + *
> + * This function copies one object at the head of the ring and
> + * moves the head index.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj
> + *   A pointer to the object to be added.
> + * @return
> + *   - 0: Success; objects enqueued.
> + *   - -ENOBUFS: Not enough room in the ring to enqueue; no object is enqueued.
> + */
> +static __rte_always_inline int
> +rte_st_ring_enqueue(struct rte_st_ring *r, void *obj)
> +{
> +	return rte_st_ring_enqueue_elem(r, &obj, sizeof(void *));
> +}
> +
> +/**
> + * Enqueue fixed number of objects on a ST ring at the tail.
> + *
> + * This function copies the objects at the tail of the ring and
> + * moves the tail index (backwards).
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_table
> + *   A pointer to a table of void * pointers (objects).
> + * @param n
> + *   The number of objects to add in the ring from the obj_table.
> + * @param free_space
> + *   if non-NULL, returns the amount of space in the ring after the
> + *   enqueue operation has finished.
> + * @return
> + *   The number of objects enqueued, either 0 or n
> + */
> +static __rte_always_inline unsigned int
> +rte_st_ring_enqueue_at_tail_bulk(struct rte_st_ring *r,
> +				 void * const *obj_table, unsigned int n,
> +				 unsigned int *free_space)
> +{
> +	return rte_st_ring_enqueue_at_tail_bulk_elem(r, obj_table,
> +			sizeof(void *), n, free_space);
> +}
> +
> +/**
> + * Enqueue upto a maximum number of objects on a ST ring at the tail.
> + *
> + * This function copies the objects at the tail of the ring and
> + * moves the tail index (backwards).
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_table
> + *   A pointer to a table of void * pointers (objects).
> + * @param n
> + *   The number of objects to add in the ring from the obj_table.
> + * @param free_space
> + *   if non-NULL, returns the amount of space in the ring after the
> + *   enqueue operation has finished.
> + * @return
> + *   - n: Actual number of objects enqueued.
> + */
> +static __rte_always_inline unsigned int
> +rte_st_ring_enqueue_at_tail_burst(struct rte_st_ring *r,
> +				  void * const *obj_table, unsigned int n,
> +				  unsigned int *free_space)
> +{
> +	return rte_st_ring_enqueue_at_tail_burst_elem(r, obj_table,
> +			sizeof(void *), n, free_space);
> +}
> +
> +/**
> + * Enqueue one object on a ST ring at tail.
> + *
> + * This function copies one object at the tail of the ring and
> + * moves the tail index (backwards).
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj
> + *   A pointer to the object to be added.
> + * @return
> + *   - 0: Success; objects enqueued.
> + *   - -ENOBUFS: Not enough room in the ring to enqueue; no object is enqueued.
> + */
> +static __rte_always_inline int
> +rte_st_ring_enqueue_at_tail(struct rte_st_ring *r, void *obj)
> +{
> +	return rte_st_ring_enqueue_at_tail_elem(r, &obj, sizeof(void *));
> +}
> +
> +/**
> + * Dequeue a fixed number of objects from a ST ring.
> + *
> + * This function copies the objects from the tail of the ring and
> + * moves the tail index.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_table
> + *   A pointer to a table of void * pointers (objects) that will be filled.
> + * @param n
> + *   The number of objects to dequeue from the ring to the obj_table.
> + * @param available
> + *   If non-NULL, returns the number of remaining ring entries after the
> + *   dequeue has finished.
> + * @return
> + *   The number of objects dequeued, either 0 or n
> + */
> +static __rte_always_inline unsigned int
> +rte_st_ring_dequeue_bulk(struct rte_st_ring *r, void **obj_table, unsigned int n,
> +		unsigned int *available)
> +{
> +	return rte_st_ring_dequeue_bulk_elem(r, obj_table, sizeof(void *),
> +			n, available);
> +}
> +
> +/**
> + * Dequeue upto a maximum number of objects from a ST ring.
> + *
> + * This function copies the objects from the tail of the ring and
> + * moves the tail index.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_table
> + *   A pointer to a table of void * pointers (objects) that will be filled.
> + * @param n
> + *   The number of objects to dequeue from the ring to the obj_table.
> + * @param available
> + *   If non-NULL, returns the number of remaining ring entries after the
> + *   dequeue has finished.
> + * @return
> + *   - Number of objects dequeued
> + */
> +static __rte_always_inline unsigned int
> +rte_st_ring_dequeue_burst(struct rte_st_ring *r, void **obj_table,
> +		unsigned int n, unsigned int *available)
> +{
> +	return rte_st_ring_dequeue_burst_elem(r, obj_table, sizeof(void *),
> +			n, available);
> +}
> +
> +/**
> + * Dequeue one object from a ST ring.
> + *
> + * This function copies one object from the tail of the ring and
> + * moves the tail index.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_p
> + *   A pointer to a void * pointer (object) that will be filled.
> + * @return
> + *   - 0: Success, objects dequeued.
> + *   - -ENOENT: Not enough entries in the ring to dequeue, no object is
> + *     dequeued.
> + */
> +static __rte_always_inline int
> +rte_st_ring_dequeue(struct rte_st_ring *r, void **obj_p)
> +{
> +	return rte_st_ring_dequeue_elem(r, obj_p, sizeof(void *));
> +}
> +
> +/**
> + * Dequeue a fixed number of objects from a ST ring from the head.
> + *
> + * This function copies the objects from the head of the ring and
> + * moves the head index (backwards).
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_table
> + *   A pointer to a table of void * pointers (objects) that will be filled.
> + * @param n
> + *   The number of objects to dequeue from the ring to the obj_table.
> + * @param available
> + *   If non-NULL, returns the number of remaining ring entries after the
> + *   dequeue has finished.
> + * @return
> + *   The number of objects dequeued, either 0 or n
> + */
> +static __rte_always_inline unsigned int
> +rte_st_ring_dequeue_at_head_bulk(struct rte_st_ring *r, void **obj_table, unsigned int n,
> +		unsigned int *available)
> +{
> +	return rte_st_ring_dequeue_bulk_elem(r, obj_table, sizeof(void *),
> +			n, available);
> +}
> +
> +/**
> + * Dequeue upto a maximum number of objects from a ST ring from the head.
> + *
> + * This function copies the objects from the head of the ring and
> + * moves the head index (backwards).
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_table
> + *   A pointer to a table of void * pointers (objects) that will be filled.
> + * @param n
> + *   The number of objects to dequeue from the ring to the obj_table.
> + * @param available
> + *   If non-NULL, returns the number of remaining ring entries after the
> + *   dequeue has finished.
> + * @return
> + *   - Number of objects dequeued
> + */
> +static __rte_always_inline unsigned int
> +rte_st_ring_dequeue_at_head_burst(struct rte_st_ring *r, void **obj_table,
> +		unsigned int n, unsigned int *available)
> +{
> +	return rte_st_ring_dequeue_burst_elem(r, obj_table, sizeof(void *),
> +			n, available);
> +}
> +
> +/**
> + * Dequeue one object from a ST ring from the head.
> + *
> + * This function copies the objects from the head of the ring and
> + * moves the head index (backwards).
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_p
> + *   A pointer to a void * pointer (object) that will be filled.
> + * @return
> + *   - 0: Success, objects dequeued.
> + *   - -ENOENT: Not enough entries in the ring to dequeue, no object is
> + *     dequeued.
> + */
> +static __rte_always_inline int
> +rte_st_ring_at_head_dequeue(struct rte_st_ring *r, void **obj_p)
> +{
> +	return rte_st_ring_dequeue_elem(r, obj_p, sizeof(void *));
> +}
> +
> +/**
> + * Flush a ST ring.
> + *
> + * This function flush all the elements in a ST ring
> + *
> + * @warning
> + * Make sure the ring is not in use while calling this function.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + */
> +void
> +rte_st_ring_reset(struct rte_st_ring *r);
> +
> +/**
> + * Return the number of entries in a ST ring.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @return
> + *   The number of entries in the ring.
> + */
> +static inline unsigned int
> +rte_st_ring_count(const struct rte_st_ring *r)
> +{
> +	uint32_t count = (r->head - r->tail) & r->mask;
> +	return count;
> +}
> +
> +/**
> + * Return the number of free entries in a ST ring.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @return
> + *   The number of free entries in the ring.
> + */
> +static inline unsigned int
> +rte_st_ring_free_count(const struct rte_st_ring *r)
> +{
> +	return r->capacity - rte_st_ring_count(r);
> +}
> +
> +/**
> + * Test if a ST ring is full.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @return
> + *   - 1: The ring is full.
> + *   - 0: The ring is not full.
> + */
> +static inline int
> +rte_st_ring_full(const struct rte_st_ring *r)
> +{
> +	return rte_st_ring_free_count(r) == 0;
> +}
> +
> +/**
> + * Test if a ST ring is empty.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @return
> + *   - 1: The ring is empty.
> + *   - 0: The ring is not empty.
> + */
> +static inline int
> +rte_st_ring_empty(const struct rte_st_ring *r)
> +{
> +	return r->tail == r->head;
> +}
> +
> +/**
> + * Return the size of the ring.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @return
> + *   The size of the data store used by the ring.
> + *   NOTE: this is not the same as the usable space in the ring. To query that
> + *   use ``rte_st_ring_get_capacity()``.
> + */
> +static inline unsigned int
> +rte_st_ring_get_size(const struct rte_st_ring *r)
> +{
> +	return r->size;
> +}
> +
> +/**
> + * Return the number of elements which can be stored in the ring.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @return
> + *   The usable size of the ring.
> + */
> +static inline unsigned int
> +rte_st_ring_get_capacity(const struct rte_st_ring *r)
> +{
> +	return r->capacity;
> +}
> +
> +/**
> + * Dump the status of all rings on the console
> + *
> + * @param f
> + *   A pointer to a file for output
> + */
> +void rte_st_ring_list_dump(FILE *f);
> +
> +/**
> + * Search a ST ring from its name
> + *
> + * @param name
> + *   The name of the ring.
> + * @return
> + *   The pointer to the ring matching the name, or NULL if not found,
> + *   with rte_errno set appropriately. Possible rte_errno values include:
> + *    - ENOENT - required entry not available to return.
> + */
> +struct rte_st_ring *rte_st_ring_lookup(const char *name);
> +
> +#ifdef __cplusplus
> +}
> +#endif
> +
> +#endif /* _RTE_ST_RING_H_ */
  
Honnappa Nagarahalli Aug. 22, 2023, 5:43 a.m. UTC | #3
> -----Original Message-----
> From: Mattias Rönnblom <hofors@lysator.liu.se>
> Sent: Monday, August 21, 2023 4:14 PM
> To: Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com>;
> jackmin@nvidia.com; konstantin.v.ananyev@yandex.ru
> Cc: dev@dpdk.org; Ruifeng Wang <Ruifeng.Wang@arm.com>; Aditya
> Ambadipudi <Aditya.Ambadipudi@arm.com>; Wathsala Wathawana
> Vithanage <wathsala.vithanage@arm.com>; nd <nd@arm.com>
> Subject: Re: [RFC] lib/st_ring: add single thread ring
> 
> On 2023-08-21 08:04, Honnappa Nagarahalli wrote:
> > Add a single thread safe and multi-thread unsafe ring data structure.
> 
> One must have set the bar very low, if one needs to specify that an API is
> single-thread safe.
> 
> > This library provides an simple and efficient alternative to
> > multi-thread safe ring when multi-thread safety is not required.
> >
> > Signed-off-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
> > ---
> > v1:
> > 1) The code is very prelimnary and is not even compiled
> > 2) This is intended to show the APIs and some thoughts on
> > implementation
> 
> If you haven't done it already, maybe it might be worth looking around in the
> code base for already-existing, more-or-less open-coded fifo/circular buffer
> type data structures. Just to make sure those can be eliminated if this makes
> it into DPDK.
> 
> There's one in rte_event_eth_rx_adapter.c, and I think one in the SW
> eventdev as well. Seems to be one in cmdline_cirbuf.h as well. I'm sure there
> are many more.
I knew there are some, but have not looked at them yet. I will look at them.

> 
> You could pick some other name for it, instead of the slightly awkward
> "st_ring" (e.g., "fifo", "cbuf", "cbuffer", "circ_buffer"). That would also leave
> you with more freedom to stray from the MT safe ring API without surprising
> the user, if needed (and I think it is needed).
The thought was to make it clear that this is for single-thread use (i.e.not even producer and consumer on different threads), may be I do not need to try hard.
"fifo" might not be good option given that dequeue/enqueue at both ends of the ring are required/allowed.
Wikipedia [1] and others [2], [3] indicates that this data structure should be called 'deque' (pronounced as deck). I would prefer to go with this (assuming this will be outside of 'rte_ring')

[1] https://en.wikipedia.org/wiki/Double-ended_queue
[2] https://www.geeksforgeeks.org/deque-set-1-introduction-applications/
[3] https://stackoverflow.com/questions/3880254/why-do-we-need-deque-data-structures-in-the-real-world#:~:text=A%20Deque%20is%20a%20double,thing%20on%20front%20of%20queue.

> 
> Hopefully you can reduce API complexity compared to the MT-safe version.
> Having a name for these kinds of data structures doesn't make a lot of sense,
> for example. Skip the dump function. Relax from always_inline to just regular
> inline.
Yes, plan is to reduce complexity (compared to rte_ring) and some APIs can be skipped until there is a need.

> 
> I'm not sure you need bulk/burst type operations. Without any memory
> fences, an optimizing compiler should do a pretty good job of unrolling
> multiple-element access type operations, assuming you leave the ST ring
> code in the header files (otherwise LTO is needed).
IMO, bulk/burst APIs are about the functionality rather than loop unrolling. APIs to work with single objects can be skipped (use bulk APIs with n=1).

> 
> I think you will want a peek-type operation on the reader side. That more for
> convenience, rather than that I think the copies will actually be there in the
> object code (such should be eliminated by the compiler, given that the
> barriers are gone).
> 
> > 3) More APIs and the rest of the implementation will come in subsequent
> >     versions
> >
> >   lib/st_ring/rte_st_ring.h | 567
> ++++++++++++++++++++++++++++++++++++++
> >   1 file changed, 567 insertions(+)
> >   create mode 100644 lib/st_ring/rte_st_ring.h
> >
> > diff --git a/lib/st_ring/rte_st_ring.h b/lib/st_ring/rte_st_ring.h new
> > file mode 100644 index 0000000000..8cb8832591
> > --- /dev/null
> > +++ b/lib/st_ring/rte_st_ring.h
> > @@ -0,0 +1,567 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(c) 2023 Arm Limited
> > + */
> > +
> > +#ifndef _RTE_ST_RING_H_
> > +#define _RTE_ST_RING_H_
> > +
> > +/**
> > + * @file
> > + * RTE Signle Thread Ring (ST Ring)
> > + *
> > + * The ST Ring is a fixed-size queue intended to be accessed
> > + * by one thread at a time. It does not provide concurrent access to
> > + * multiple threads. If there are multiple threads accessing the ST
> > +ring,
> > + * then the threads have to use locks to protect the ring from
> > + * getting corrupted.
> 
> You are basically saying the same thing three times here.
> 
> > + *
> > + * - FIFO (First In First Out)
> > + * - Maximum size is fixed; the pointers are stored in a table.
> > + * - Consumer and producer part of same thread.
> > + * - Multi-thread producers and consumers need locking.
> 
> ...two more times here. One might get the impression you really don't trust
> the reader.
> 
> > + * - Single/Bulk/burst dequeue at Tail or Head
> > + * - Single/Bulk/burst enqueue at Head or Tail
> 
> Does this not sound more like a deque, than a FIFO/circular buffer? Are there
> any examples where this functionality (the double-endedness) is needed in
> the DPDK code base?
I see, you are calling it 'deque' as well. Basically, this patch originated due to a requirement in MLX PMD [1]

[1] https://github.com/DPDK/dpdk/blob/main/drivers/net/mlx5/mlx5_hws_cnt.h#L381

> 
> > + *
> > + */
> > +
> > +#ifdef __cplusplus
> > +extern "C" {
> > +#endif
> > +
> > +#include <rte_st_ring_core.h>
> > +#include <rte_st_ring_elem.h>
> 
> Is the intention to provide a ring with compile-time variable element size? In
> other words, where the elements of a particular ring instance has the same
> element size, but different rings may have different element sizes.
> 
> Seems like a good idea to me, in that case. Although often you will have
> pointers, it would be useful to store larger things like small structs, and
> maybe smaller elements as well.
Yes, the idea is to make the element size flexible and also compile-time constant.

> 
> > +
> > +/**
> > + * Calculate the memory size needed for a ST ring
> > + *
> > + * This function returns the number of bytes needed for a ST ring,
> > +given
> > + * the number of elements in it. This value is the sum of the size of
> > + * the structure rte_st_ring and the size of the memory needed by the
> > + * elements. The value is aligned to a cache line size.
> > + *
> > + * @param count
> > + *   The number of elements in the ring (must be a power of 2).
> > + * @return
> > + *   - The memory size needed for the ST ring on success.
> > + *   - -EINVAL if count is not a power of 2.
> > + */
> > +ssize_t rte_st_ring_get_memsize(unsigned int count);
> > +
> > +/**
> > + * Initialize a ST ring structure.
> > + *
> > + * Initialize a ST ring structure in memory pointed by "r". The size
> > +of the
> > + * memory area must be large enough to store the ring structure and
> > +the
> > + * object table. It is advised to use rte_st_ring_get_memsize() to
> > +get the
> > + * appropriate size.
> > + *
> > + * The ST ring size is set to *count*, which must be a power of two.
> > + * The real usable ring size is *count-1* instead of *count* to
> > + * differentiate a full ring from an empty ring.
> > + *
> > + * The ring is not added in RTE_TAILQ_ST_RING global list. Indeed,
> > +the
> > + * memory given by the caller may not be shareable among dpdk
> > + * processes.
> > + *
> > + * @param r
> > + *   The pointer to the ring structure followed by the elements table.
> > + * @param name
> > + *   The name of the ring.
> > + * @param count
> > + *   The number of elements in the ring (must be a power of 2,
> > + *   unless RTE_ST_RING_F_EXACT_SZ is set in flags).
> > + * @param flags
> > + *   An OR of the following:
> > + *   - RTE_ST_RING_F_EXACT_SZ: If this flag is set, the ring will hold
> > + *     exactly the requested number of entries, and the requested size
> > + *     will be rounded up to the next power of two, but the usable space
> > + *     will be exactly that requested. Worst case, if a power-of-2 size is
> > + *     requested, half the ring space will be wasted.
> > + *     Without this flag set, the ring size requested must be a power of 2,
> > + *     and the usable space will be that size - 1.
> > + * @return
> > + *   0 on success, or a negative value on error.
> > + */
> > +int rte_st_ring_init(struct rte_st_ring *r, const char *name,
> > +	unsigned int count, unsigned int flags);
> > +
> > +/**
> > + * Create a new ST ring named *name* in memory.
> > + *
> > + * This function uses ``memzone_reserve()`` to allocate memory. Then
> > +it
> > + * calls rte_st_ring_init() to initialize an empty ring.
> > + *
> > + * The new ring size is set to *count*, which must be a power of two.
> > + * The real usable ring size is *count-1* instead of *count* to
> > + * differentiate a full ring from an empty ring.
> > + *
> > + * The ring is added in RTE_TAILQ_ST_RING list.
> > + *
> > + * @param name
> > + *   The name of the ring.
> > + * @param count
> > + *   The size of the ring (must be a power of 2,
> > + *   unless RTE_ST_RING_F_EXACT_SZ is set in flags).
> > + * @param socket_id
> > + *   The *socket_id* argument is the socket identifier in case of
> > + *   NUMA. The value can be *SOCKET_ID_ANY* if there is no NUMA
> > + *   constraint for the reserved zone.
> > + * @param flags
> > + *   - RTE_ST_RING_F_EXACT_SZ: If this flag is set, the ring will hold exactly
> the
> > + *     requested number of entries, and the requested size will be rounded
> up
> > + *     to the next power of two, but the usable space will be exactly that
> > + *     requested. Worst case, if a power-of-2 size is requested, half the
> > + *     ring space will be wasted.
> > + *     Without this flag set, the ring size requested must be a power of 2,
> > + *     and the usable space will be that size - 1.
> > + * @return
> > + *   On success, the pointer to the new allocated ring. NULL on error with
> > + *    rte_errno set appropriately. Possible errno values include:
> > + *    - E_RTE_NO_CONFIG - function could not get pointer to rte_config
> structure
> > + *    - EINVAL - count provided is not a power of 2
> > + *    - ENOSPC - the maximum number of memzones has already been
> allocated
> > + *    - EEXIST - a memzone with the same name already exists
> > + *    - ENOMEM - no appropriate memory area found in which to create
> memzone
> > + */
> > +struct rte_st_ring *rte_st_ring_create(const char *name, unsigned int
> count,
> > +				 int socket_id, unsigned int flags);
> > +
> > +/**
> > + * De-allocate all memory used by the ring.
> > + *
> > + * @param r
> > + *   Ring to free.
> > + *   If NULL then, the function does nothing.
> > + */
> > +void rte_st_ring_free(struct rte_st_ring *r);
> > +
> > +/**
> > + * Dump the status of the ring to a file.
> > + *
> > + * @param f
> > + *   A pointer to a file for output
> > + * @param r
> > + *   A pointer to the ring structure.
> > + */
> > +void rte_st_ring_dump(FILE *f, const struct rte_st_ring *r);
> > +
> > +/**
> > + * Enqueue fixed number of objects on a ST ring.
> > + *
> > + * This function copies the objects at the head of the ring and
> > + * moves the head index.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_table
> > + *   A pointer to a table of void * pointers (objects).
> > + * @param n
> > + *   The number of objects to add in the ring from the obj_table.
> > + * @param free_space
> > + *   if non-NULL, returns the amount of space in the ring after the
> > + *   enqueue operation has finished.
> > + * @return
> > + *   The number of objects enqueued, either 0 or n
> > + */
> > +static __rte_always_inline unsigned int
> > +rte_st_ring_enqueue_bulk(struct rte_st_ring *r, void * const *obj_table,
> > +		      unsigned int n, unsigned int *free_space) {
> > +	return rte_st_ring_enqueue_bulk_elem(r, obj_table, sizeof(void *),
> > +			n, free_space);
> > +}
> > +
> > +/**
> > + * Enqueue upto a maximum number of objects on a ST ring.
> > + *
> > + * This function copies the objects at the head of the ring and
> > + * moves the head index.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_table
> > + *   A pointer to a table of void * pointers (objects).
> > + * @param n
> > + *   The number of objects to add in the ring from the obj_table.
> > + * @param free_space
> > + *   if non-NULL, returns the amount of space in the ring after the
> > + *   enqueue operation has finished.
> > + * @return
> > + *   - n: Actual number of objects enqueued.
> > + */
> > +static __rte_always_inline unsigned int
> > +rte_st_ring_enqueue_burst(struct rte_st_ring *r, void * const *obj_table,
> > +		      unsigned int n, unsigned int *free_space) {
> > +	return rte_st_ring_enqueue_burst_elem(r, obj_table, sizeof(void *),
> > +			n, free_space);
> > +}
> > +
> > +/**
> > + * Enqueue one object on a ST ring.
> > + *
> > + * This function copies one object at the head of the ring and
> > + * moves the head index.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj
> > + *   A pointer to the object to be added.
> > + * @return
> > + *   - 0: Success; objects enqueued.
> > + *   - -ENOBUFS: Not enough room in the ring to enqueue; no object is
> enqueued.
> > + */
> > +static __rte_always_inline int
> > +rte_st_ring_enqueue(struct rte_st_ring *r, void *obj) {
> > +	return rte_st_ring_enqueue_elem(r, &obj, sizeof(void *)); }
> > +
> > +/**
> > + * Enqueue fixed number of objects on a ST ring at the tail.
> > + *
> > + * This function copies the objects at the tail of the ring and
> > + * moves the tail index (backwards).
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_table
> > + *   A pointer to a table of void * pointers (objects).
> > + * @param n
> > + *   The number of objects to add in the ring from the obj_table.
> > + * @param free_space
> > + *   if non-NULL, returns the amount of space in the ring after the
> > + *   enqueue operation has finished.
> > + * @return
> > + *   The number of objects enqueued, either 0 or n
> > + */
> > +static __rte_always_inline unsigned int
> > +rte_st_ring_enqueue_at_tail_bulk(struct rte_st_ring *r,
> > +				 void * const *obj_table, unsigned int n,
> > +				 unsigned int *free_space)
> > +{
> > +	return rte_st_ring_enqueue_at_tail_bulk_elem(r, obj_table,
> > +			sizeof(void *), n, free_space);
> > +}
> > +
> > +/**
> > + * Enqueue upto a maximum number of objects on a ST ring at the tail.
> > + *
> > + * This function copies the objects at the tail of the ring and
> > + * moves the tail index (backwards).
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_table
> > + *   A pointer to a table of void * pointers (objects).
> > + * @param n
> > + *   The number of objects to add in the ring from the obj_table.
> > + * @param free_space
> > + *   if non-NULL, returns the amount of space in the ring after the
> > + *   enqueue operation has finished.
> > + * @return
> > + *   - n: Actual number of objects enqueued.
> > + */
> > +static __rte_always_inline unsigned int
> > +rte_st_ring_enqueue_at_tail_burst(struct rte_st_ring *r,
> > +				  void * const *obj_table, unsigned int n,
> > +				  unsigned int *free_space)
> > +{
> > +	return rte_st_ring_enqueue_at_tail_burst_elem(r, obj_table,
> > +			sizeof(void *), n, free_space);
> > +}
> > +
> > +/**
> > + * Enqueue one object on a ST ring at tail.
> > + *
> > + * This function copies one object at the tail of the ring and
> > + * moves the tail index (backwards).
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj
> > + *   A pointer to the object to be added.
> > + * @return
> > + *   - 0: Success; objects enqueued.
> > + *   - -ENOBUFS: Not enough room in the ring to enqueue; no object is
> enqueued.
> > + */
> > +static __rte_always_inline int
> > +rte_st_ring_enqueue_at_tail(struct rte_st_ring *r, void *obj) {
> > +	return rte_st_ring_enqueue_at_tail_elem(r, &obj, sizeof(void *)); }
> > +
> > +/**
> > + * Dequeue a fixed number of objects from a ST ring.
> > + *
> > + * This function copies the objects from the tail of the ring and
> > + * moves the tail index.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_table
> > + *   A pointer to a table of void * pointers (objects) that will be filled.
> > + * @param n
> > + *   The number of objects to dequeue from the ring to the obj_table.
> > + * @param available
> > + *   If non-NULL, returns the number of remaining ring entries after the
> > + *   dequeue has finished.
> > + * @return
> > + *   The number of objects dequeued, either 0 or n
> > + */
> > +static __rte_always_inline unsigned int
> > +rte_st_ring_dequeue_bulk(struct rte_st_ring *r, void **obj_table,
> unsigned int n,
> > +		unsigned int *available)
> > +{
> > +	return rte_st_ring_dequeue_bulk_elem(r, obj_table, sizeof(void *),
> > +			n, available);
> > +}
> > +
> > +/**
> > + * Dequeue upto a maximum number of objects from a ST ring.
> > + *
> > + * This function copies the objects from the tail of the ring and
> > + * moves the tail index.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_table
> > + *   A pointer to a table of void * pointers (objects) that will be filled.
> > + * @param n
> > + *   The number of objects to dequeue from the ring to the obj_table.
> > + * @param available
> > + *   If non-NULL, returns the number of remaining ring entries after the
> > + *   dequeue has finished.
> > + * @return
> > + *   - Number of objects dequeued
> > + */
> > +static __rte_always_inline unsigned int
> > +rte_st_ring_dequeue_burst(struct rte_st_ring *r, void **obj_table,
> > +		unsigned int n, unsigned int *available) {
> > +	return rte_st_ring_dequeue_burst_elem(r, obj_table, sizeof(void *),
> > +			n, available);
> > +}
> > +
> > +/**
> > + * Dequeue one object from a ST ring.
> > + *
> > + * This function copies one object from the tail of the ring and
> > + * moves the tail index.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_p
> > + *   A pointer to a void * pointer (object) that will be filled.
> > + * @return
> > + *   - 0: Success, objects dequeued.
> > + *   - -ENOENT: Not enough entries in the ring to dequeue, no object is
> > + *     dequeued.
> > + */
> > +static __rte_always_inline int
> > +rte_st_ring_dequeue(struct rte_st_ring *r, void **obj_p) {
> > +	return rte_st_ring_dequeue_elem(r, obj_p, sizeof(void *)); }
> > +
> > +/**
> > + * Dequeue a fixed number of objects from a ST ring from the head.
> > + *
> > + * This function copies the objects from the head of the ring and
> > + * moves the head index (backwards).
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_table
> > + *   A pointer to a table of void * pointers (objects) that will be filled.
> > + * @param n
> > + *   The number of objects to dequeue from the ring to the obj_table.
> > + * @param available
> > + *   If non-NULL, returns the number of remaining ring entries after the
> > + *   dequeue has finished.
> > + * @return
> > + *   The number of objects dequeued, either 0 or n
> > + */
> > +static __rte_always_inline unsigned int
> > +rte_st_ring_dequeue_at_head_bulk(struct rte_st_ring *r, void
> **obj_table, unsigned int n,
> > +		unsigned int *available)
> > +{
> > +	return rte_st_ring_dequeue_bulk_elem(r, obj_table, sizeof(void *),
> > +			n, available);
> > +}
> > +
> > +/**
> > + * Dequeue upto a maximum number of objects from a ST ring from the
> head.
> > + *
> > + * This function copies the objects from the head of the ring and
> > + * moves the head index (backwards).
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_table
> > + *   A pointer to a table of void * pointers (objects) that will be filled.
> > + * @param n
> > + *   The number of objects to dequeue from the ring to the obj_table.
> > + * @param available
> > + *   If non-NULL, returns the number of remaining ring entries after the
> > + *   dequeue has finished.
> > + * @return
> > + *   - Number of objects dequeued
> > + */
> > +static __rte_always_inline unsigned int
> > +rte_st_ring_dequeue_at_head_burst(struct rte_st_ring *r, void
> **obj_table,
> > +		unsigned int n, unsigned int *available) {
> > +	return rte_st_ring_dequeue_burst_elem(r, obj_table, sizeof(void *),
> > +			n, available);
> > +}
> > +
> > +/**
> > + * Dequeue one object from a ST ring from the head.
> > + *
> > + * This function copies the objects from the head of the ring and
> > + * moves the head index (backwards).
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_p
> > + *   A pointer to a void * pointer (object) that will be filled.
> > + * @return
> > + *   - 0: Success, objects dequeued.
> > + *   - -ENOENT: Not enough entries in the ring to dequeue, no object is
> > + *     dequeued.
> > + */
> > +static __rte_always_inline int
> > +rte_st_ring_at_head_dequeue(struct rte_st_ring *r, void **obj_p) {
> > +	return rte_st_ring_dequeue_elem(r, obj_p, sizeof(void *)); }
> > +
> > +/**
> > + * Flush a ST ring.
> > + *
> > + * This function flush all the elements in a ST ring
> > + *
> > + * @warning
> > + * Make sure the ring is not in use while calling this function.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + */
> > +void
> > +rte_st_ring_reset(struct rte_st_ring *r);
> > +
> > +/**
> > + * Return the number of entries in a ST ring.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @return
> > + *   The number of entries in the ring.
> > + */
> > +static inline unsigned int
> > +rte_st_ring_count(const struct rte_st_ring *r) {
> > +	uint32_t count = (r->head - r->tail) & r->mask;
> > +	return count;
> > +}
> > +
> > +/**
> > + * Return the number of free entries in a ST ring.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @return
> > + *   The number of free entries in the ring.
> > + */
> > +static inline unsigned int
> > +rte_st_ring_free_count(const struct rte_st_ring *r) {
> > +	return r->capacity - rte_st_ring_count(r); }
> > +
> > +/**
> > + * Test if a ST ring is full.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @return
> > + *   - 1: The ring is full.
> > + *   - 0: The ring is not full.
> > + */
> > +static inline int
> > +rte_st_ring_full(const struct rte_st_ring *r) {
> > +	return rte_st_ring_free_count(r) == 0; }
> > +
> > +/**
> > + * Test if a ST ring is empty.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @return
> > + *   - 1: The ring is empty.
> > + *   - 0: The ring is not empty.
> > + */
> > +static inline int
> > +rte_st_ring_empty(const struct rte_st_ring *r) {
> > +	return r->tail == r->head;
> > +}
> > +
> > +/**
> > + * Return the size of the ring.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @return
> > + *   The size of the data store used by the ring.
> > + *   NOTE: this is not the same as the usable space in the ring. To query that
> > + *   use ``rte_st_ring_get_capacity()``.
> > + */
> > +static inline unsigned int
> > +rte_st_ring_get_size(const struct rte_st_ring *r) {
> > +	return r->size;
> > +}
> > +
> > +/**
> > + * Return the number of elements which can be stored in the ring.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @return
> > + *   The usable size of the ring.
> > + */
> > +static inline unsigned int
> > +rte_st_ring_get_capacity(const struct rte_st_ring *r) {
> > +	return r->capacity;
> > +}
> > +
> > +/**
> > + * Dump the status of all rings on the console
> > + *
> > + * @param f
> > + *   A pointer to a file for output
> > + */
> > +void rte_st_ring_list_dump(FILE *f);
> > +
> > +/**
> > + * Search a ST ring from its name
> > + *
> > + * @param name
> > + *   The name of the ring.
> > + * @return
> > + *   The pointer to the ring matching the name, or NULL if not found,
> > + *   with rte_errno set appropriately. Possible rte_errno values include:
> > + *    - ENOENT - required entry not available to return.
> > + */
> > +struct rte_st_ring *rte_st_ring_lookup(const char *name);
> > +
> > +#ifdef __cplusplus
> > +}
> > +#endif
> > +
> > +#endif /* _RTE_ST_RING_H_ */
  
Honnappa Nagarahalli Aug. 22, 2023, 5:47 a.m. UTC | #4
> -----Original Message-----
> From: Morten Brørup <mb@smartsharesystems.com>
> Sent: Monday, August 21, 2023 2:37 AM
> To: Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com>;
> jackmin@nvidia.com; konstantin.v.ananyev@yandex.ru
> Cc: dev@dpdk.org; Ruifeng Wang <Ruifeng.Wang@arm.com>; Aditya
> Ambadipudi <Aditya.Ambadipudi@arm.com>; Wathsala Wathawana Vithanage
> <wathsala.vithanage@arm.com>; nd <nd@arm.com>
> Subject: RE: [RFC] lib/st_ring: add single thread ring
> 
> > From: Honnappa Nagarahalli [mailto:honnappa.nagarahalli@arm.com]
> > Sent: Monday, 21 August 2023 08.04
> >
> > Add a single thread safe and multi-thread unsafe ring data structure.
> > This library provides an simple and efficient alternative to multi-
> > thread safe ring when multi-thread safety is not required.
> >
> > Signed-off-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
> > ---
> 
> Good idea.
> 
> However, I prefer it to be implemented in the ring lib as one more ring type.
> That would also give us a lot of the infrastructure (management functions,
> documentation and tests) for free.
IMO, the current code for rte_ring seems complex with C11 and generic implementations, APIs for pointer objects vs APIs for flexible element size etc. I did not want to introduce one more flavor and make it more complex.
The requirements are different as well. For ex: single thread ring needs APIs for dequeuing and enqueuing at both ends of the ring which is not applicable to existing RTE ring.

But, I see how the existing infra can be reused easily.

> 
> The ring lib already has performance-optimized APIs for single-consumer and
> single-producer use, rte_ring_sc_dequeue_bulk() and
> rte_ring_sp_enqueue_burst(). Similar performance-optimized APIs for single-
> thread use could be added: rte_ring_st_dequeue_bulk() and
> rte_ring_st_enqueue_burst().
Yes, the names look fine.
Looking through the code. We have the sync type enum:

/** prod/cons sync types */
enum rte_ring_sync_type {
        RTE_RING_SYNC_MT,     /**< multi-thread safe (default mode) */
        RTE_RING_SYNC_ST,     /**< single thread only */
        RTE_RING_SYNC_MT_RTS, /**< multi-thread relaxed tail sync */
        RTE_RING_SYNC_MT_HTS, /**< multi-thread head/tail sync */
};

The type RTE_RING_SYNC_ST needs better explanation (not a problem). But, this name would have been ideal to use for single thread ring.
This enum does not need to be exposed to the users. However, there are rte_ring_get_prod/cons_sync_type etc which seem to be exposed to the user.
This all means, we need to have a sync type name RTE_RING_SYNC_MT_UNSAFE (any other better name?) which then affects API naming. rte_ring_mt_unsafe_dequeue_bulk?

> 
> Regardless if added to the ring lib or as a separate lib, "reverse" APIs (for single-
> thread use only) and zero-copy APIs can be added at any time later.
  
Mattias Rönnblom Aug. 22, 2023, 8:04 a.m. UTC | #5
On 2023-08-22 07:43, Honnappa Nagarahalli wrote:
> 
> 
>> -----Original Message-----
>> From: Mattias Rönnblom <hofors@lysator.liu.se>
>> Sent: Monday, August 21, 2023 4:14 PM
>> To: Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com>;
>> jackmin@nvidia.com; konstantin.v.ananyev@yandex.ru
>> Cc: dev@dpdk.org; Ruifeng Wang <Ruifeng.Wang@arm.com>; Aditya
>> Ambadipudi <Aditya.Ambadipudi@arm.com>; Wathsala Wathawana
>> Vithanage <wathsala.vithanage@arm.com>; nd <nd@arm.com>
>> Subject: Re: [RFC] lib/st_ring: add single thread ring
>>
>> On 2023-08-21 08:04, Honnappa Nagarahalli wrote:
>>> Add a single thread safe and multi-thread unsafe ring data structure.
>>
>> One must have set the bar very low, if one needs to specify that an API is
>> single-thread safe.
>>
>>> This library provides an simple and efficient alternative to
>>> multi-thread safe ring when multi-thread safety is not required.
>>>
>>> Signed-off-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
>>> ---
>>> v1:
>>> 1) The code is very prelimnary and is not even compiled
>>> 2) This is intended to show the APIs and some thoughts on
>>> implementation
>>
>> If you haven't done it already, maybe it might be worth looking around in the
>> code base for already-existing, more-or-less open-coded fifo/circular buffer
>> type data structures. Just to make sure those can be eliminated if this makes
>> it into DPDK.
>>
>> There's one in rte_event_eth_rx_adapter.c, and I think one in the SW
>> eventdev as well. Seems to be one in cmdline_cirbuf.h as well. I'm sure there
>> are many more.
> I knew there are some, but have not looked at them yet. I will look at them.
> 
>>
>> You could pick some other name for it, instead of the slightly awkward
>> "st_ring" (e.g., "fifo", "cbuf", "cbuffer", "circ_buffer"). That would also leave
>> you with more freedom to stray from the MT safe ring API without surprising
>> the user, if needed (and I think it is needed).
> The thought was to make it clear that this is for single-thread use (i.e.not even producer and consumer on different threads), may be I do not need to try hard.
> "fifo" might not be good option given that dequeue/enqueue at both ends of the ring are required/allowed.
> Wikipedia [1] and others [2], [3] indicates that this data structure should be called 'deque' (pronounced as deck). I would prefer to go with this (assuming this will be outside of 'rte_ring')
> 
> [1] https://en.wikipedia.org/wiki/Double-ended_queue
> [2] https://www.geeksforgeeks.org/deque-set-1-introduction-applications/
> [3] https://stackoverflow.com/questions/3880254/why-do-we-need-deque-data-structures-in-the-real-world#:~:text=A%20Deque%20is%20a%20double,thing%20on%20front%20of%20queue.
> 
>>
>> Hopefully you can reduce API complexity compared to the MT-safe version.
>> Having a name for these kinds of data structures doesn't make a lot of sense,
>> for example. Skip the dump function. Relax from always_inline to just regular
>> inline.
> Yes, plan is to reduce complexity (compared to rte_ring) and some APIs can be skipped until there is a need.
> 
>>
>> I'm not sure you need bulk/burst type operations. Without any memory
>> fences, an optimizing compiler should do a pretty good job of unrolling
>> multiple-element access type operations, assuming you leave the ST ring
>> code in the header files (otherwise LTO is needed).
> IMO, bulk/burst APIs are about the functionality rather than loop unrolling. APIs to work with single objects can be skipped (use bulk APIs with n=1).
> 

Given that this data structure will often be use in conjunction with 
other burst/bulk type operations, I agree.

What about peek? I guess you could have a burst/bulk peek as well, would 
that operation be needed. I think it will be needed, but the 
introduction of such API elements could always be deferred.

>>
>> I think you will want a peek-type operation on the reader side. That more for
>> convenience, rather than that I think the copies will actually be there in the
>> object code (such should be eliminated by the compiler, given that the
>> barriers are gone).
>>
>>> 3) More APIs and the rest of the implementation will come in subsequent
>>>      versions
>>>
>>>    lib/st_ring/rte_st_ring.h | 567
>> ++++++++++++++++++++++++++++++++++++++
>>>    1 file changed, 567 insertions(+)
>>>    create mode 100644 lib/st_ring/rte_st_ring.h
>>>
>>> diff --git a/lib/st_ring/rte_st_ring.h b/lib/st_ring/rte_st_ring.h new
>>> file mode 100644 index 0000000000..8cb8832591
>>> --- /dev/null
>>> +++ b/lib/st_ring/rte_st_ring.h
>>> @@ -0,0 +1,567 @@
>>> +/* SPDX-License-Identifier: BSD-3-Clause
>>> + * Copyright(c) 2023 Arm Limited
>>> + */
>>> +
>>> +#ifndef _RTE_ST_RING_H_
>>> +#define _RTE_ST_RING_H_
>>> +
>>> +/**
>>> + * @file
>>> + * RTE Signle Thread Ring (ST Ring)
>>> + *
>>> + * The ST Ring is a fixed-size queue intended to be accessed
>>> + * by one thread at a time. It does not provide concurrent access to
>>> + * multiple threads. If there are multiple threads accessing the ST
>>> +ring,
>>> + * then the threads have to use locks to protect the ring from
>>> + * getting corrupted.
>>
>> You are basically saying the same thing three times here.
>>
>>> + *
>>> + * - FIFO (First In First Out)
>>> + * - Maximum size is fixed; the pointers are stored in a table.
>>> + * - Consumer and producer part of same thread.
>>> + * - Multi-thread producers and consumers need locking.
>>
>> ...two more times here. One might get the impression you really don't trust
>> the reader.
>>
>>> + * - Single/Bulk/burst dequeue at Tail or Head
>>> + * - Single/Bulk/burst enqueue at Head or Tail
>>
>> Does this not sound more like a deque, than a FIFO/circular buffer? Are there
>> any examples where this functionality (the double-endedness) is needed in
>> the DPDK code base?
> I see, you are calling it 'deque' as well. Basically, this patch originated due to a requirement in MLX PMD [1]
> 
> [1] https://github.com/DPDK/dpdk/blob/main/drivers/net/mlx5/mlx5_hws_cnt.h#L381
> 
>>
>>> + *
>>> + */
>>> +
>>> +#ifdef __cplusplus
>>> +extern "C" {
>>> +#endif
>>> +
>>> +#include <rte_st_ring_core.h>
>>> +#include <rte_st_ring_elem.h>
>>
>> Is the intention to provide a ring with compile-time variable element size? In
>> other words, where the elements of a particular ring instance has the same
>> element size, but different rings may have different element sizes.
>>
>> Seems like a good idea to me, in that case. Although often you will have
>> pointers, it would be useful to store larger things like small structs, and
>> maybe smaller elements as well.
> Yes, the idea is to make the element size flexible and also compile-time constant.
> 
>>
>>> +
>>> +/**
>>> + * Calculate the memory size needed for a ST ring
>>> + *
>>> + * This function returns the number of bytes needed for a ST ring,
>>> +given
>>> + * the number of elements in it. This value is the sum of the size of
>>> + * the structure rte_st_ring and the size of the memory needed by the
>>> + * elements. The value is aligned to a cache line size.
>>> + *
>>> + * @param count
>>> + *   The number of elements in the ring (must be a power of 2).
>>> + * @return
>>> + *   - The memory size needed for the ST ring on success.
>>> + *   - -EINVAL if count is not a power of 2.
>>> + */
>>> +ssize_t rte_st_ring_get_memsize(unsigned int count);
>>> +
>>> +/**
>>> + * Initialize a ST ring structure.
>>> + *
>>> + * Initialize a ST ring structure in memory pointed by "r". The size
>>> +of the
>>> + * memory area must be large enough to store the ring structure and
>>> +the
>>> + * object table. It is advised to use rte_st_ring_get_memsize() to
>>> +get the
>>> + * appropriate size.
>>> + *
>>> + * The ST ring size is set to *count*, which must be a power of two.
>>> + * The real usable ring size is *count-1* instead of *count* to
>>> + * differentiate a full ring from an empty ring.
>>> + *
>>> + * The ring is not added in RTE_TAILQ_ST_RING global list. Indeed,
>>> +the
>>> + * memory given by the caller may not be shareable among dpdk
>>> + * processes.
>>> + *
>>> + * @param r
>>> + *   The pointer to the ring structure followed by the elements table.
>>> + * @param name
>>> + *   The name of the ring.
>>> + * @param count
>>> + *   The number of elements in the ring (must be a power of 2,
>>> + *   unless RTE_ST_RING_F_EXACT_SZ is set in flags).
>>> + * @param flags
>>> + *   An OR of the following:
>>> + *   - RTE_ST_RING_F_EXACT_SZ: If this flag is set, the ring will hold
>>> + *     exactly the requested number of entries, and the requested size
>>> + *     will be rounded up to the next power of two, but the usable space
>>> + *     will be exactly that requested. Worst case, if a power-of-2 size is
>>> + *     requested, half the ring space will be wasted.
>>> + *     Without this flag set, the ring size requested must be a power of 2,
>>> + *     and the usable space will be that size - 1.
>>> + * @return
>>> + *   0 on success, or a negative value on error.
>>> + */
>>> +int rte_st_ring_init(struct rte_st_ring *r, const char *name,
>>> +	unsigned int count, unsigned int flags);
>>> +
>>> +/**
>>> + * Create a new ST ring named *name* in memory.
>>> + *
>>> + * This function uses ``memzone_reserve()`` to allocate memory. Then
>>> +it
>>> + * calls rte_st_ring_init() to initialize an empty ring.
>>> + *
>>> + * The new ring size is set to *count*, which must be a power of two.
>>> + * The real usable ring size is *count-1* instead of *count* to
>>> + * differentiate a full ring from an empty ring.
>>> + *
>>> + * The ring is added in RTE_TAILQ_ST_RING list.
>>> + *
>>> + * @param name
>>> + *   The name of the ring.
>>> + * @param count
>>> + *   The size of the ring (must be a power of 2,
>>> + *   unless RTE_ST_RING_F_EXACT_SZ is set in flags).
>>> + * @param socket_id
>>> + *   The *socket_id* argument is the socket identifier in case of
>>> + *   NUMA. The value can be *SOCKET_ID_ANY* if there is no NUMA
>>> + *   constraint for the reserved zone.
>>> + * @param flags
>>> + *   - RTE_ST_RING_F_EXACT_SZ: If this flag is set, the ring will hold exactly
>> the
>>> + *     requested number of entries, and the requested size will be rounded
>> up
>>> + *     to the next power of two, but the usable space will be exactly that
>>> + *     requested. Worst case, if a power-of-2 size is requested, half the
>>> + *     ring space will be wasted.
>>> + *     Without this flag set, the ring size requested must be a power of 2,
>>> + *     and the usable space will be that size - 1.
>>> + * @return
>>> + *   On success, the pointer to the new allocated ring. NULL on error with
>>> + *    rte_errno set appropriately. Possible errno values include:
>>> + *    - E_RTE_NO_CONFIG - function could not get pointer to rte_config
>> structure
>>> + *    - EINVAL - count provided is not a power of 2
>>> + *    - ENOSPC - the maximum number of memzones has already been
>> allocated
>>> + *    - EEXIST - a memzone with the same name already exists
>>> + *    - ENOMEM - no appropriate memory area found in which to create
>> memzone
>>> + */
>>> +struct rte_st_ring *rte_st_ring_create(const char *name, unsigned int
>> count,
>>> +				 int socket_id, unsigned int flags);
>>> +
>>> +/**
>>> + * De-allocate all memory used by the ring.
>>> + *
>>> + * @param r
>>> + *   Ring to free.
>>> + *   If NULL then, the function does nothing.
>>> + */
>>> +void rte_st_ring_free(struct rte_st_ring *r);
>>> +
>>> +/**
>>> + * Dump the status of the ring to a file.
>>> + *
>>> + * @param f
>>> + *   A pointer to a file for output
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + */
>>> +void rte_st_ring_dump(FILE *f, const struct rte_st_ring *r);
>>> +
>>> +/**
>>> + * Enqueue fixed number of objects on a ST ring.
>>> + *
>>> + * This function copies the objects at the head of the ring and
>>> + * moves the head index.
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @param obj_table
>>> + *   A pointer to a table of void * pointers (objects).
>>> + * @param n
>>> + *   The number of objects to add in the ring from the obj_table.
>>> + * @param free_space
>>> + *   if non-NULL, returns the amount of space in the ring after the
>>> + *   enqueue operation has finished.
>>> + * @return
>>> + *   The number of objects enqueued, either 0 or n
>>> + */
>>> +static __rte_always_inline unsigned int
>>> +rte_st_ring_enqueue_bulk(struct rte_st_ring *r, void * const *obj_table,
>>> +		      unsigned int n, unsigned int *free_space) {
>>> +	return rte_st_ring_enqueue_bulk_elem(r, obj_table, sizeof(void *),
>>> +			n, free_space);
>>> +}
>>> +
>>> +/**
>>> + * Enqueue upto a maximum number of objects on a ST ring.
>>> + *
>>> + * This function copies the objects at the head of the ring and
>>> + * moves the head index.
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @param obj_table
>>> + *   A pointer to a table of void * pointers (objects).
>>> + * @param n
>>> + *   The number of objects to add in the ring from the obj_table.
>>> + * @param free_space
>>> + *   if non-NULL, returns the amount of space in the ring after the
>>> + *   enqueue operation has finished.
>>> + * @return
>>> + *   - n: Actual number of objects enqueued.
>>> + */
>>> +static __rte_always_inline unsigned int
>>> +rte_st_ring_enqueue_burst(struct rte_st_ring *r, void * const *obj_table,
>>> +		      unsigned int n, unsigned int *free_space) {
>>> +	return rte_st_ring_enqueue_burst_elem(r, obj_table, sizeof(void *),
>>> +			n, free_space);
>>> +}
>>> +
>>> +/**
>>> + * Enqueue one object on a ST ring.
>>> + *
>>> + * This function copies one object at the head of the ring and
>>> + * moves the head index.
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @param obj
>>> + *   A pointer to the object to be added.
>>> + * @return
>>> + *   - 0: Success; objects enqueued.
>>> + *   - -ENOBUFS: Not enough room in the ring to enqueue; no object is
>> enqueued.
>>> + */
>>> +static __rte_always_inline int
>>> +rte_st_ring_enqueue(struct rte_st_ring *r, void *obj) {
>>> +	return rte_st_ring_enqueue_elem(r, &obj, sizeof(void *)); }
>>> +
>>> +/**
>>> + * Enqueue fixed number of objects on a ST ring at the tail.
>>> + *
>>> + * This function copies the objects at the tail of the ring and
>>> + * moves the tail index (backwards).
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @param obj_table
>>> + *   A pointer to a table of void * pointers (objects).
>>> + * @param n
>>> + *   The number of objects to add in the ring from the obj_table.
>>> + * @param free_space
>>> + *   if non-NULL, returns the amount of space in the ring after the
>>> + *   enqueue operation has finished.
>>> + * @return
>>> + *   The number of objects enqueued, either 0 or n
>>> + */
>>> +static __rte_always_inline unsigned int
>>> +rte_st_ring_enqueue_at_tail_bulk(struct rte_st_ring *r,
>>> +				 void * const *obj_table, unsigned int n,
>>> +				 unsigned int *free_space)
>>> +{
>>> +	return rte_st_ring_enqueue_at_tail_bulk_elem(r, obj_table,
>>> +			sizeof(void *), n, free_space);
>>> +}
>>> +
>>> +/**
>>> + * Enqueue upto a maximum number of objects on a ST ring at the tail.
>>> + *
>>> + * This function copies the objects at the tail of the ring and
>>> + * moves the tail index (backwards).
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @param obj_table
>>> + *   A pointer to a table of void * pointers (objects).
>>> + * @param n
>>> + *   The number of objects to add in the ring from the obj_table.
>>> + * @param free_space
>>> + *   if non-NULL, returns the amount of space in the ring after the
>>> + *   enqueue operation has finished.
>>> + * @return
>>> + *   - n: Actual number of objects enqueued.
>>> + */
>>> +static __rte_always_inline unsigned int
>>> +rte_st_ring_enqueue_at_tail_burst(struct rte_st_ring *r,
>>> +				  void * const *obj_table, unsigned int n,
>>> +				  unsigned int *free_space)
>>> +{
>>> +	return rte_st_ring_enqueue_at_tail_burst_elem(r, obj_table,
>>> +			sizeof(void *), n, free_space);
>>> +}
>>> +
>>> +/**
>>> + * Enqueue one object on a ST ring at tail.
>>> + *
>>> + * This function copies one object at the tail of the ring and
>>> + * moves the tail index (backwards).
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @param obj
>>> + *   A pointer to the object to be added.
>>> + * @return
>>> + *   - 0: Success; objects enqueued.
>>> + *   - -ENOBUFS: Not enough room in the ring to enqueue; no object is
>> enqueued.
>>> + */
>>> +static __rte_always_inline int
>>> +rte_st_ring_enqueue_at_tail(struct rte_st_ring *r, void *obj) {
>>> +	return rte_st_ring_enqueue_at_tail_elem(r, &obj, sizeof(void *)); }
>>> +
>>> +/**
>>> + * Dequeue a fixed number of objects from a ST ring.
>>> + *
>>> + * This function copies the objects from the tail of the ring and
>>> + * moves the tail index.
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @param obj_table
>>> + *   A pointer to a table of void * pointers (objects) that will be filled.
>>> + * @param n
>>> + *   The number of objects to dequeue from the ring to the obj_table.
>>> + * @param available
>>> + *   If non-NULL, returns the number of remaining ring entries after the
>>> + *   dequeue has finished.
>>> + * @return
>>> + *   The number of objects dequeued, either 0 or n
>>> + */
>>> +static __rte_always_inline unsigned int
>>> +rte_st_ring_dequeue_bulk(struct rte_st_ring *r, void **obj_table,
>> unsigned int n,
>>> +		unsigned int *available)
>>> +{
>>> +	return rte_st_ring_dequeue_bulk_elem(r, obj_table, sizeof(void *),
>>> +			n, available);
>>> +}
>>> +
>>> +/**
>>> + * Dequeue upto a maximum number of objects from a ST ring.
>>> + *
>>> + * This function copies the objects from the tail of the ring and
>>> + * moves the tail index.
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @param obj_table
>>> + *   A pointer to a table of void * pointers (objects) that will be filled.
>>> + * @param n
>>> + *   The number of objects to dequeue from the ring to the obj_table.
>>> + * @param available
>>> + *   If non-NULL, returns the number of remaining ring entries after the
>>> + *   dequeue has finished.
>>> + * @return
>>> + *   - Number of objects dequeued
>>> + */
>>> +static __rte_always_inline unsigned int
>>> +rte_st_ring_dequeue_burst(struct rte_st_ring *r, void **obj_table,
>>> +		unsigned int n, unsigned int *available) {
>>> +	return rte_st_ring_dequeue_burst_elem(r, obj_table, sizeof(void *),
>>> +			n, available);
>>> +}
>>> +
>>> +/**
>>> + * Dequeue one object from a ST ring.
>>> + *
>>> + * This function copies one object from the tail of the ring and
>>> + * moves the tail index.
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @param obj_p
>>> + *   A pointer to a void * pointer (object) that will be filled.
>>> + * @return
>>> + *   - 0: Success, objects dequeued.
>>> + *   - -ENOENT: Not enough entries in the ring to dequeue, no object is
>>> + *     dequeued.
>>> + */
>>> +static __rte_always_inline int
>>> +rte_st_ring_dequeue(struct rte_st_ring *r, void **obj_p) {
>>> +	return rte_st_ring_dequeue_elem(r, obj_p, sizeof(void *)); }
>>> +
>>> +/**
>>> + * Dequeue a fixed number of objects from a ST ring from the head.
>>> + *
>>> + * This function copies the objects from the head of the ring and
>>> + * moves the head index (backwards).
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @param obj_table
>>> + *   A pointer to a table of void * pointers (objects) that will be filled.
>>> + * @param n
>>> + *   The number of objects to dequeue from the ring to the obj_table.
>>> + * @param available
>>> + *   If non-NULL, returns the number of remaining ring entries after the
>>> + *   dequeue has finished.
>>> + * @return
>>> + *   The number of objects dequeued, either 0 or n
>>> + */
>>> +static __rte_always_inline unsigned int
>>> +rte_st_ring_dequeue_at_head_bulk(struct rte_st_ring *r, void
>> **obj_table, unsigned int n,
>>> +		unsigned int *available)
>>> +{
>>> +	return rte_st_ring_dequeue_bulk_elem(r, obj_table, sizeof(void *),
>>> +			n, available);
>>> +}
>>> +
>>> +/**
>>> + * Dequeue upto a maximum number of objects from a ST ring from the
>> head.
>>> + *
>>> + * This function copies the objects from the head of the ring and
>>> + * moves the head index (backwards).
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @param obj_table
>>> + *   A pointer to a table of void * pointers (objects) that will be filled.
>>> + * @param n
>>> + *   The number of objects to dequeue from the ring to the obj_table.
>>> + * @param available
>>> + *   If non-NULL, returns the number of remaining ring entries after the
>>> + *   dequeue has finished.
>>> + * @return
>>> + *   - Number of objects dequeued
>>> + */
>>> +static __rte_always_inline unsigned int
>>> +rte_st_ring_dequeue_at_head_burst(struct rte_st_ring *r, void
>> **obj_table,
>>> +		unsigned int n, unsigned int *available) {
>>> +	return rte_st_ring_dequeue_burst_elem(r, obj_table, sizeof(void *),
>>> +			n, available);
>>> +}
>>> +
>>> +/**
>>> + * Dequeue one object from a ST ring from the head.
>>> + *
>>> + * This function copies the objects from the head of the ring and
>>> + * moves the head index (backwards).
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @param obj_p
>>> + *   A pointer to a void * pointer (object) that will be filled.
>>> + * @return
>>> + *   - 0: Success, objects dequeued.
>>> + *   - -ENOENT: Not enough entries in the ring to dequeue, no object is
>>> + *     dequeued.
>>> + */
>>> +static __rte_always_inline int
>>> +rte_st_ring_at_head_dequeue(struct rte_st_ring *r, void **obj_p) {
>>> +	return rte_st_ring_dequeue_elem(r, obj_p, sizeof(void *)); }
>>> +
>>> +/**
>>> + * Flush a ST ring.
>>> + *
>>> + * This function flush all the elements in a ST ring
>>> + *
>>> + * @warning
>>> + * Make sure the ring is not in use while calling this function.
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + */
>>> +void
>>> +rte_st_ring_reset(struct rte_st_ring *r);
>>> +
>>> +/**
>>> + * Return the number of entries in a ST ring.
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @return
>>> + *   The number of entries in the ring.
>>> + */
>>> +static inline unsigned int
>>> +rte_st_ring_count(const struct rte_st_ring *r) {
>>> +	uint32_t count = (r->head - r->tail) & r->mask;
>>> +	return count;
>>> +}
>>> +
>>> +/**
>>> + * Return the number of free entries in a ST ring.
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @return
>>> + *   The number of free entries in the ring.
>>> + */
>>> +static inline unsigned int
>>> +rte_st_ring_free_count(const struct rte_st_ring *r) {
>>> +	return r->capacity - rte_st_ring_count(r); }
>>> +
>>> +/**
>>> + * Test if a ST ring is full.
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @return
>>> + *   - 1: The ring is full.
>>> + *   - 0: The ring is not full.
>>> + */
>>> +static inline int
>>> +rte_st_ring_full(const struct rte_st_ring *r) {
>>> +	return rte_st_ring_free_count(r) == 0; }
>>> +
>>> +/**
>>> + * Test if a ST ring is empty.
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @return
>>> + *   - 1: The ring is empty.
>>> + *   - 0: The ring is not empty.
>>> + */
>>> +static inline int
>>> +rte_st_ring_empty(const struct rte_st_ring *r) {
>>> +	return r->tail == r->head;
>>> +}
>>> +
>>> +/**
>>> + * Return the size of the ring.
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @return
>>> + *   The size of the data store used by the ring.
>>> + *   NOTE: this is not the same as the usable space in the ring. To query that
>>> + *   use ``rte_st_ring_get_capacity()``.
>>> + */
>>> +static inline unsigned int
>>> +rte_st_ring_get_size(const struct rte_st_ring *r) {
>>> +	return r->size;
>>> +}
>>> +
>>> +/**
>>> + * Return the number of elements which can be stored in the ring.
>>> + *
>>> + * @param r
>>> + *   A pointer to the ring structure.
>>> + * @return
>>> + *   The usable size of the ring.
>>> + */
>>> +static inline unsigned int
>>> +rte_st_ring_get_capacity(const struct rte_st_ring *r) {
>>> +	return r->capacity;
>>> +}
>>> +
>>> +/**
>>> + * Dump the status of all rings on the console
>>> + *
>>> + * @param f
>>> + *   A pointer to a file for output
>>> + */
>>> +void rte_st_ring_list_dump(FILE *f);
>>> +
>>> +/**
>>> + * Search a ST ring from its name
>>> + *
>>> + * @param name
>>> + *   The name of the ring.
>>> + * @return
>>> + *   The pointer to the ring matching the name, or NULL if not found,
>>> + *   with rte_errno set appropriately. Possible rte_errno values include:
>>> + *    - ENOENT - required entry not available to return.
>>> + */
>>> +struct rte_st_ring *rte_st_ring_lookup(const char *name);
>>> +
>>> +#ifdef __cplusplus
>>> +}
>>> +#endif
>>> +
>>> +#endif /* _RTE_ST_RING_H_ */
  
Honnappa Nagarahalli Aug. 22, 2023, 4:28 p.m. UTC | #6
<snip>

> >> Subject: Re: [RFC] lib/st_ring: add single thread ring
> >>
> >> On 2023-08-21 08:04, Honnappa Nagarahalli wrote:
> >>> Add a single thread safe and multi-thread unsafe ring data structure.
> >>
> >> One must have set the bar very low, if one needs to specify that an
> >> API is single-thread safe.
> >>
> >>> This library provides an simple and efficient alternative to
> >>> multi-thread safe ring when multi-thread safety is not required.
> >>>
> >>> Signed-off-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
> >>> ---
> >>> v1:
> >>> 1) The code is very prelimnary and is not even compiled
> >>> 2) This is intended to show the APIs and some thoughts on
> >>> implementation
> >>
> >> If you haven't done it already, maybe it might be worth looking
> >> around in the code base for already-existing, more-or-less open-coded
> >> fifo/circular buffer type data structures. Just to make sure those
> >> can be eliminated if this makes it into DPDK.
> >>
> >> There's one in rte_event_eth_rx_adapter.c, and I think one in the SW
> >> eventdev as well. Seems to be one in cmdline_cirbuf.h as well. I'm
> >> sure there are many more.
> > I knew there are some, but have not looked at them yet. I will look at them.
> >
> >>
> >> You could pick some other name for it, instead of the slightly
> >> awkward "st_ring" (e.g., "fifo", "cbuf", "cbuffer", "circ_buffer").
> >> That would also leave you with more freedom to stray from the MT safe
> >> ring API without surprising the user, if needed (and I think it is needed).
> > The thought was to make it clear that this is for single-thread use (i.e.not even
> producer and consumer on different threads), may be I do not need to try hard.
> > "fifo" might not be good option given that dequeue/enqueue at both ends of
> the ring are required/allowed.
> > Wikipedia [1] and others [2], [3] indicates that this data structure
> > should be called 'deque' (pronounced as deck). I would prefer to go
> > with this (assuming this will be outside of 'rte_ring')
> >
> > [1] https://en.wikipedia.org/wiki/Double-ended_queue
> > [2]
> > https://www.geeksforgeeks.org/deque-set-1-introduction-applications/
> > [3] https://stackoverflow.com/questions/3880254/why-do-we-need-deque-
> data-structures-in-the-real-
> world#:~:text=A%20Deque%20is%20a%20double,thing%20on%20front%20of
> %20queue.
> >
> >>
> >> Hopefully you can reduce API complexity compared to the MT-safe version.
> >> Having a name for these kinds of data structures doesn't make a lot
> >> of sense, for example. Skip the dump function. Relax from
> >> always_inline to just regular inline.
> > Yes, plan is to reduce complexity (compared to rte_ring) and some APIs can be
> skipped until there is a need.
> >
> >>
> >> I'm not sure you need bulk/burst type operations. Without any memory
> >> fences, an optimizing compiler should do a pretty good job of
> >> unrolling multiple-element access type operations, assuming you leave
> >> the ST ring code in the header files (otherwise LTO is needed).
> > IMO, bulk/burst APIs are about the functionality rather than loop unrolling.
> APIs to work with single objects can be skipped (use bulk APIs with n=1).
> >
> 
> Given that this data structure will often be use in conjunction with other
> burst/bulk type operations, I agree.
> 
> What about peek? I guess you could have a burst/bulk peek as well, would that
> operation be needed. I think it will be needed, but the introduction of such API
> elements could always be deferred.
Yes, I will provide this functionality, but will differ for later.

> 
> >>
> >> I think you will want a peek-type operation on the reader side. That
> >> more for convenience, rather than that I think the copies will
> >> actually be there in the object code (such should be eliminated by
> >> the compiler, given that the barriers are gone).
> >>
> >>> 3) More APIs and the rest of the implementation will come in subsequent
> >>>      versions
> >>>
> >>>    lib/st_ring/rte_st_ring.h | 567
> >> ++++++++++++++++++++++++++++++++++++++
> >>>    1 file changed, 567 insertions(+)
> >>>    create mode 100644 lib/st_ring/rte_st_ring.h
> >>>
> >>> diff --git a/lib/st_ring/rte_st_ring.h b/lib/st_ring/rte_st_ring.h
> >>> new file mode 100644 index 0000000000..8cb8832591
> >>> --- /dev/null
> >>> +++ b/lib/st_ring/rte_st_ring.h
> >>> @@ -0,0 +1,567 @@
> >>> +/* SPDX-License-Identifier: BSD-3-Clause
> >>> + * Copyright(c) 2023 Arm Limited
> >>> + */
> >>> +
> >>> +#ifndef _RTE_ST_RING_H_
> >>> +#define _RTE_ST_RING_H_
> >>> +
> >>> +/**
> >>> + * @file
> >>> + * RTE Signle Thread Ring (ST Ring)
> >>> + *
> >>> + * The ST Ring is a fixed-size queue intended to be accessed
> >>> + * by one thread at a time. It does not provide concurrent access
> >>> +to
> >>> + * multiple threads. If there are multiple threads accessing the ST
> >>> +ring,
> >>> + * then the threads have to use locks to protect the ring from
> >>> + * getting corrupted.
> >>
> >> You are basically saying the same thing three times here.
> >>
> >>> + *
> >>> + * - FIFO (First In First Out)
> >>> + * - Maximum size is fixed; the pointers are stored in a table.
> >>> + * - Consumer and producer part of same thread.
> >>> + * - Multi-thread producers and consumers need locking.
> >>
> >> ...two more times here. One might get the impression you really don't
> >> trust the reader.
> >>
> >>> + * - Single/Bulk/burst dequeue at Tail or Head
> >>> + * - Single/Bulk/burst enqueue at Head or Tail
> >>
> >> Does this not sound more like a deque, than a FIFO/circular buffer?
> >> Are there any examples where this functionality (the
> >> double-endedness) is needed in the DPDK code base?
> > I see, you are calling it 'deque' as well. Basically, this patch
> > originated due to a requirement in MLX PMD [1]
> >
> > [1]
> > https://github.com/DPDK/dpdk/blob/main/drivers/net/mlx5/mlx5_hws_cnt.h
> > #L381
> >
> >>
> >>> + *
> >>> + */
> >>> +
> >>> +#ifdef __cplusplus
> >>> +extern "C" {
> >>> +#endif
> >>> +
> >>> +#include <rte_st_ring_core.h>
> >>> +#include <rte_st_ring_elem.h>
> >>
> >> Is the intention to provide a ring with compile-time variable element
> >> size? In other words, where the elements of a particular ring
> >> instance has the same element size, but different rings may have different
> element sizes.
> >>
> >> Seems like a good idea to me, in that case. Although often you will
> >> have pointers, it would be useful to store larger things like small
> >> structs, and maybe smaller elements as well.
> > Yes, the idea is to make the element size flexible and also compile-time
> constant.
> >
> >>
> >>> +
> >>> +/**

<snip>
  
Morten Brørup Aug. 24, 2023, 8:05 a.m. UTC | #7
> From: Honnappa Nagarahalli [mailto:Honnappa.Nagarahalli@arm.com]
> Sent: Tuesday, 22 August 2023 07.47
> 
> > From: Morten Brørup <mb@smartsharesystems.com>
> > Sent: Monday, August 21, 2023 2:37 AM
> >
> > > From: Honnappa Nagarahalli [mailto:honnappa.nagarahalli@arm.com]
> > > Sent: Monday, 21 August 2023 08.04
> > >
> > > Add a single thread safe and multi-thread unsafe ring data structure.
> > > This library provides an simple and efficient alternative to multi-
> > > thread safe ring when multi-thread safety is not required.
> > >
> > > Signed-off-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
> > > ---
> >
> > Good idea.
> >
> > However, I prefer it to be implemented in the ring lib as one more ring
> type.
> > That would also give us a lot of the infrastructure (management functions,
> > documentation and tests) for free.
> IMO, the current code for rte_ring seems complex with C11 and generic
> implementations, APIs for pointer objects vs APIs for flexible element size
> etc. I did not want to introduce one more flavor and make it more complex.

From the user perspective, I think one more ring flavor is less complex than an entirely separate (very similar) library with its own set of (very similar) APIs.

I agree that the ring lib has grown somewhat over-engineered, but please don't use that as an argument for making the same-thread ring a separate lib.

On the other hand: If the addition of an optimized same-thread ring flavor would require too many invasive modifications of the existing ring lib, I would accept that as an argument for not adding it as another ring flavor to the existing ring lib.

> The requirements are different as well. For ex: single thread ring needs APIs
> for dequeuing and enqueuing at both ends of the ring which is not applicable
> to existing RTE ring.

Yes, I will address this topic at the end of this mail.

> 
> But, I see how the existing infra can be reused easily.

This also goes for future infrastructure. I doubt that new infrastructure added to the ring lib will also be added to the same-thread ring lib... for reference, consider the PMDs containing copy-pasted code from the mempool lib... none of the later improvements of the mempool lib were implemented in those PMDs.

In essence, I think this lib overlaps the existing ring lib too much to justify making it a separate lib.

> 
> >
> > The ring lib already has performance-optimized APIs for single-consumer and
> > single-producer use, rte_ring_sc_dequeue_bulk() and
> > rte_ring_sp_enqueue_burst(). Similar performance-optimized APIs for single-
> > thread use could be added: rte_ring_st_dequeue_bulk() and
> > rte_ring_st_enqueue_burst().
> Yes, the names look fine.
> Looking through the code. We have the sync type enum:
> 
> /** prod/cons sync types */
> enum rte_ring_sync_type {
>         RTE_RING_SYNC_MT,     /**< multi-thread safe (default mode) */
>         RTE_RING_SYNC_ST,     /**< single thread only */
>         RTE_RING_SYNC_MT_RTS, /**< multi-thread relaxed tail sync */
>         RTE_RING_SYNC_MT_HTS, /**< multi-thread head/tail sync */
> };
> 
> The type RTE_RING_SYNC_ST needs better explanation (not a problem). But, this
> name would have been ideal to use for single thread ring.
> This enum does not need to be exposed to the users. However, there are
> rte_ring_get_prod/cons_sync_type etc which seem to be exposed to the user.
> This all means, we need to have a sync type name RTE_RING_SYNC_MT_UNSAFE (any
> other better name?) which then affects API naming.
> rte_ring_mt_unsafe_dequeue_bulk?

As always, naming is difficult.
The enum rte_ring_sync_type describes the producer and consumer independently, whereas this ring type uses the same thread for both producer and consumer.
I think we should avoid MT in the names for this variant. How about:

RTE_RING_SYNC_STPC /**< same thread for both producer and consumer */

And:

rte_ring_spc_dequeue_bulk() and rte_ring_spc_enqueue_burst()

> 
> >
> > Regardless if added to the ring lib or as a separate lib, "reverse" APIs
> (for single-
> > thread use only) and zero-copy APIs can be added at any time later.

As the only current use case for "reverse" (i.e. dequeue at tail, enqueue at head) APIs is for the same-thread ring flavor, we could start by adding only the specialized variants of the "reverse" APIs, rte_ring_spc_reverse_xxx(), and initially omit the generic rte_ring_reverse_xxx() APIs. (We need better names; I used "reverse" for explanation only.)
  
Mattias Rönnblom Aug. 24, 2023, 10:52 a.m. UTC | #8
On 2023-08-24 10:05, Morten Brørup wrote:
>> From: Honnappa Nagarahalli [mailto:Honnappa.Nagarahalli@arm.com]
>> Sent: Tuesday, 22 August 2023 07.47
>>
>>> From: Morten Brørup <mb@smartsharesystems.com>
>>> Sent: Monday, August 21, 2023 2:37 AM
>>>
>>>> From: Honnappa Nagarahalli [mailto:honnappa.nagarahalli@arm.com]
>>>> Sent: Monday, 21 August 2023 08.04
>>>>
>>>> Add a single thread safe and multi-thread unsafe ring data structure.
>>>> This library provides an simple and efficient alternative to multi-
>>>> thread safe ring when multi-thread safety is not required.
>>>>
>>>> Signed-off-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
>>>> ---
>>>
>>> Good idea.
>>>
>>> However, I prefer it to be implemented in the ring lib as one more ring
>> type.
>>> That would also give us a lot of the infrastructure (management functions,
>>> documentation and tests) for free.
>> IMO, the current code for rte_ring seems complex with C11 and generic
>> implementations, APIs for pointer objects vs APIs for flexible element size
>> etc. I did not want to introduce one more flavor and make it more complex.
> 
>  From the user perspective, I think one more ring flavor is less complex than an entirely separate (very similar) library with its own set of (very similar) APIs.
> 
> I agree that the ring lib has grown somewhat over-engineered, but please don't use that as an argument for making the same-thread ring a separate lib.
> 

What's being proposed is a double-ended queue, not a ring (in the DPDK 
sense).

If you want to Swiss army knifify the rte_ring further and make it a 
deque, then rte_stack should scrapped as well, since it's will become 
just a subset of the new rte_ring_now_really_a_deque.

> On the other hand: If the addition of an optimized same-thread ring flavor would require too many invasive modifications of the existing ring lib, I would accept that as an argument for not adding it as another ring flavor to the existing ring lib.
> 
>> The requirements are different as well. For ex: single thread ring needs APIs
>> for dequeuing and enqueuing at both ends of the ring which is not applicable
>> to existing RTE ring.
> 
> Yes, I will address this topic at the end of this mail.
> 
>>
>> But, I see how the existing infra can be reused easily.
> 
> This also goes for future infrastructure. I doubt that new infrastructure added to the ring lib will also be added to the same-thread ring lib... for reference, consider the PMDs containing copy-pasted code from the mempool lib... none of the later improvements of the mempool lib were implemented in those PMDs.
> 
> In essence, I think this lib overlaps the existing ring lib too much to justify making it a separate lib.
> 
>>
>>>
>>> The ring lib already has performance-optimized APIs for single-consumer and
>>> single-producer use, rte_ring_sc_dequeue_bulk() and
>>> rte_ring_sp_enqueue_burst(). Similar performance-optimized APIs for single-
>>> thread use could be added: rte_ring_st_dequeue_bulk() and
>>> rte_ring_st_enqueue_burst().
>> Yes, the names look fine.
>> Looking through the code. We have the sync type enum:
>>
>> /** prod/cons sync types */
>> enum rte_ring_sync_type {
>>          RTE_RING_SYNC_MT,     /**< multi-thread safe (default mode) */
>>          RTE_RING_SYNC_ST,     /**< single thread only */
>>          RTE_RING_SYNC_MT_RTS, /**< multi-thread relaxed tail sync */
>>          RTE_RING_SYNC_MT_HTS, /**< multi-thread head/tail sync */
>> };
>>
>> The type RTE_RING_SYNC_ST needs better explanation (not a problem). But, this
>> name would have been ideal to use for single thread ring.
>> This enum does not need to be exposed to the users. However, there are
>> rte_ring_get_prod/cons_sync_type etc which seem to be exposed to the user.
>> This all means, we need to have a sync type name RTE_RING_SYNC_MT_UNSAFE (any
>> other better name?) which then affects API naming.
>> rte_ring_mt_unsafe_dequeue_bulk?
> 
> As always, naming is difficult.
> The enum rte_ring_sync_type describes the producer and consumer independently, whereas this ring type uses the same thread for both producer and consumer.
> I think we should avoid MT in the names for this variant. How about:
> 
> RTE_RING_SYNC_STPC /**< same thread for both producer and consumer */
> 
> And:
> 
> rte_ring_spc_dequeue_bulk() and rte_ring_spc_enqueue_burst()
> 
>>
>>>
>>> Regardless if added to the ring lib or as a separate lib, "reverse" APIs
>> (for single-
>>> thread use only) and zero-copy APIs can be added at any time later.
> 
> As the only current use case for "reverse" (i.e. dequeue at tail, enqueue at head) APIs is for the same-thread ring flavor, we could start by adding only the specialized variants of the "reverse" APIs, rte_ring_spc_reverse_xxx(), and initially omit the generic rte_ring_reverse_xxx() APIs. (We need better names; I used "reverse" for explanation only.)
>
  
Morten Brørup Aug. 24, 2023, 11:22 a.m. UTC | #9
> From: Mattias Rönnblom [mailto:hofors@lysator.liu.se]
> Sent: Thursday, 24 August 2023 12.53
> 
> On 2023-08-24 10:05, Morten Brørup wrote:
> >> From: Honnappa Nagarahalli [mailto:Honnappa.Nagarahalli@arm.com]
> >> Sent: Tuesday, 22 August 2023 07.47
> >>
> >>> From: Morten Brørup <mb@smartsharesystems.com>
> >>> Sent: Monday, August 21, 2023 2:37 AM
> >>>
> >>>> From: Honnappa Nagarahalli [mailto:honnappa.nagarahalli@arm.com]
> >>>> Sent: Monday, 21 August 2023 08.04
> >>>>
> >>>> Add a single thread safe and multi-thread unsafe ring data structure.
> >>>> This library provides an simple and efficient alternative to multi-
> >>>> thread safe ring when multi-thread safety is not required.
> >>>>
> >>>> Signed-off-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
> >>>> ---
> >>>
> >>> Good idea.
> >>>
> >>> However, I prefer it to be implemented in the ring lib as one more ring
> >> type.
> >>> That would also give us a lot of the infrastructure (management functions,
> >>> documentation and tests) for free.
> >> IMO, the current code for rte_ring seems complex with C11 and generic
> >> implementations, APIs for pointer objects vs APIs for flexible element size
> >> etc. I did not want to introduce one more flavor and make it more complex.
> >
> >  From the user perspective, I think one more ring flavor is less complex
> than an entirely separate (very similar) library with its own set of (very
> similar) APIs.
> >
> > I agree that the ring lib has grown somewhat over-engineered, but please
> don't use that as an argument for making the same-thread ring a separate lib.
> >
> 
> What's being proposed is a double-ended queue, not a ring (in the DPDK
> sense).
> 
> If you want to Swiss army knifify the rte_ring further and make it a
> deque, then rte_stack should scrapped as well, since it's will become
> just a subset of the new rte_ring_now_really_a_deque.

OK. I accept that argument for not hacking it into the ring lib.

Then I will suggest that the new "deque" library should be designed with multi-threading in mind, like its two sibling libs (ring and stack). This makes it easier to use, and leaves it open for expansion to other flavors in the future.

It is perfectly acceptable that the first version only supports the same-thread deque flavor, and only the same-thread specialized APIs are exposed. I don't require any APIs or implementations supporting single-threaded (individual producer/consumer threads) or multi-threaded flavors, I only request that the design and API resemble those of its two sibling libraries. (And if there are no use cases for multi-threading flavors, they might never be added to this lib.)

> 
> > On the other hand: If the addition of an optimized same-thread ring flavor
> would require too many invasive modifications of the existing ring lib, I
> would accept that as an argument for not adding it as another ring flavor to
> the existing ring lib.
> >
> >> The requirements are different as well. For ex: single thread ring needs
> APIs
> >> for dequeuing and enqueuing at both ends of the ring which is not
> applicable
> >> to existing RTE ring.
> >
> > Yes, I will address this topic at the end of this mail.
> >
> >>
> >> But, I see how the existing infra can be reused easily.
> >
> > This also goes for future infrastructure. I doubt that new infrastructure
> added to the ring lib will also be added to the same-thread ring lib... for
> reference, consider the PMDs containing copy-pasted code from the mempool
> lib... none of the later improvements of the mempool lib were implemented in
> those PMDs.
> >
> > In essence, I think this lib overlaps the existing ring lib too much to
> justify making it a separate lib.
> >
> >>
> >>>
> >>> The ring lib already has performance-optimized APIs for single-consumer
> and
> >>> single-producer use, rte_ring_sc_dequeue_bulk() and
> >>> rte_ring_sp_enqueue_burst(). Similar performance-optimized APIs for
> single-
> >>> thread use could be added: rte_ring_st_dequeue_bulk() and
> >>> rte_ring_st_enqueue_burst().
> >> Yes, the names look fine.
> >> Looking through the code. We have the sync type enum:
> >>
> >> /** prod/cons sync types */
> >> enum rte_ring_sync_type {
> >>          RTE_RING_SYNC_MT,     /**< multi-thread safe (default mode) */
> >>          RTE_RING_SYNC_ST,     /**< single thread only */
> >>          RTE_RING_SYNC_MT_RTS, /**< multi-thread relaxed tail sync */
> >>          RTE_RING_SYNC_MT_HTS, /**< multi-thread head/tail sync */
> >> };
> >>
> >> The type RTE_RING_SYNC_ST needs better explanation (not a problem). But,
> this
> >> name would have been ideal to use for single thread ring.
> >> This enum does not need to be exposed to the users. However, there are
> >> rte_ring_get_prod/cons_sync_type etc which seem to be exposed to the user.
> >> This all means, we need to have a sync type name RTE_RING_SYNC_MT_UNSAFE
> (any
> >> other better name?) which then affects API naming.
> >> rte_ring_mt_unsafe_dequeue_bulk?
> >
> > As always, naming is difficult.
> > The enum rte_ring_sync_type describes the producer and consumer
> independently, whereas this ring type uses the same thread for both producer
> and consumer.
> > I think we should avoid MT in the names for this variant. How about:
> >
> > RTE_RING_SYNC_STPC /**< same thread for both producer and consumer */
> >
> > And:
> >
> > rte_ring_spc_dequeue_bulk() and rte_ring_spc_enqueue_burst()
> >
> >>
> >>>
> >>> Regardless if added to the ring lib or as a separate lib, "reverse" APIs
> >> (for single-
> >>> thread use only) and zero-copy APIs can be added at any time later.
> >
> > As the only current use case for "reverse" (i.e. dequeue at tail, enqueue at
> head) APIs is for the same-thread ring flavor, we could start by adding only
> the specialized variants of the "reverse" APIs, rte_ring_spc_reverse_xxx(),
> and initially omit the generic rte_ring_reverse_xxx() APIs. (We need better
> names; I used "reverse" for explanation only.)
> >
  
Honnappa Nagarahalli Aug. 26, 2023, 11:34 p.m. UTC | #10
<snip>

> 
> > From: Mattias Rönnblom [mailto:hofors@lysator.liu.se]
> > Sent: Thursday, 24 August 2023 12.53
> >
> > On 2023-08-24 10:05, Morten Brørup wrote:
> > >> From: Honnappa Nagarahalli [mailto:Honnappa.Nagarahalli@arm.com]
> > >> Sent: Tuesday, 22 August 2023 07.47
> > >>
> > >>> From: Morten Brørup <mb@smartsharesystems.com>
> > >>> Sent: Monday, August 21, 2023 2:37 AM
> > >>>
> > >>>> From: Honnappa Nagarahalli
> [mailto:honnappa.nagarahalli@arm.com]
> > >>>> Sent: Monday, 21 August 2023 08.04
> > >>>>
> > >>>> Add a single thread safe and multi-thread unsafe ring data structure.
> > >>>> This library provides an simple and efficient alternative to
> > >>>> multi- thread safe ring when multi-thread safety is not required.
> > >>>>
> > >>>> Signed-off-by: Honnappa Nagarahalli
> > >>>> <honnappa.nagarahalli@arm.com>
> > >>>> ---
> > >>>
> > >>> Good idea.
> > >>>
> > >>> However, I prefer it to be implemented in the ring lib as one more
> > >>> ring
> > >> type.
> > >>> That would also give us a lot of the infrastructure (management
> > >>> functions, documentation and tests) for free.
> > >> IMO, the current code for rte_ring seems complex with C11 and
> > >> generic implementations, APIs for pointer objects vs APIs for
> > >> flexible element size etc. I did not want to introduce one more flavor and
> make it more complex.
> > >
> > >  From the user perspective, I think one more ring flavor is less
> > > complex
> > than an entirely separate (very similar) library with its own set of
> > (very
> > similar) APIs.
> > >
> > > I agree that the ring lib has grown somewhat over-engineered, but
> > > please
> > don't use that as an argument for making the same-thread ring a separate
> lib.
> > >
> >
> > What's being proposed is a double-ended queue, not a ring (in the DPDK
> > sense).
> >
> > If you want to Swiss army knifify the rte_ring further and make it a
> > deque, then rte_stack should scrapped as well, since it's will become
> > just a subset of the new rte_ring_now_really_a_deque.
> 
> OK. I accept that argument for not hacking it into the ring lib.
> 
> Then I will suggest that the new "deque" library should be designed with
> multi-threading in mind, like its two sibling libs (ring and stack). This makes it
> easier to use, and leaves it open for expansion to other flavors in the future.
> 
> It is perfectly acceptable that the first version only supports the same-thread
> deque flavor, and only the same-thread specialized APIs are exposed. I don't
> require any APIs or implementations supporting single-threaded (individual
> producer/consumer threads) or multi-threaded flavors, I only request that the
> design and API resemble those of its two sibling libraries. (And if there are no
> use cases for multi-threading flavors, they might never be added to this lib.)
+1, will aim for this

> 
> >
> > > On the other hand: If the addition of an optimized same-thread ring
> > > flavor
> > would require too many invasive modifications of the existing ring
> > lib, I would accept that as an argument for not adding it as another
> > ring flavor to the existing ring lib.
> > >
> > >> The requirements are different as well. For ex: single thread ring
> > >> needs
> > APIs
> > >> for dequeuing and enqueuing at both ends of the ring which is not
> > applicable
> > >> to existing RTE ring.
> > >
> > > Yes, I will address this topic at the end of this mail.
> > >
> > >>
> > >> But, I see how the existing infra can be reused easily.
> > >
> > > This also goes for future infrastructure. I doubt that new
> > > infrastructure
> > added to the ring lib will also be added to the same-thread ring
> > lib... for reference, consider the PMDs containing copy-pasted code
> > from the mempool lib... none of the later improvements of the mempool
> > lib were implemented in those PMDs.
> > >
> > > In essence, I think this lib overlaps the existing ring lib too much
> > > to
> > justify making it a separate lib.
> > >
> > >>
> > >>>
> > >>> The ring lib already has performance-optimized APIs for
> > >>> single-consumer
> > and
> > >>> single-producer use, rte_ring_sc_dequeue_bulk() and
> > >>> rte_ring_sp_enqueue_burst(). Similar performance-optimized APIs
> > >>> for
> > single-
> > >>> thread use could be added: rte_ring_st_dequeue_bulk() and
> > >>> rte_ring_st_enqueue_burst().
> > >> Yes, the names look fine.
> > >> Looking through the code. We have the sync type enum:
> > >>
> > >> /** prod/cons sync types */
> > >> enum rte_ring_sync_type {
> > >>          RTE_RING_SYNC_MT,     /**< multi-thread safe (default mode) */
> > >>          RTE_RING_SYNC_ST,     /**< single thread only */
> > >>          RTE_RING_SYNC_MT_RTS, /**< multi-thread relaxed tail sync */
> > >>          RTE_RING_SYNC_MT_HTS, /**< multi-thread head/tail sync */
> > >> };
> > >>
> > >> The type RTE_RING_SYNC_ST needs better explanation (not a problem).
> > >> But,
> > this
> > >> name would have been ideal to use for single thread ring.
> > >> This enum does not need to be exposed to the users. However, there
> > >> are rte_ring_get_prod/cons_sync_type etc which seem to be exposed to
> the user.
> > >> This all means, we need to have a sync type name
> > >> RTE_RING_SYNC_MT_UNSAFE
> > (any
> > >> other better name?) which then affects API naming.
> > >> rte_ring_mt_unsafe_dequeue_bulk?
> > >
> > > As always, naming is difficult.
> > > The enum rte_ring_sync_type describes the producer and consumer
> > independently, whereas this ring type uses the same thread for both
> > producer and consumer.
> > > I think we should avoid MT in the names for this variant. How about:
> > >
> > > RTE_RING_SYNC_STPC /**< same thread for both producer and consumer
> > > */
> > >
> > > And:
> > >
> > > rte_ring_spc_dequeue_bulk() and rte_ring_spc_enqueue_burst()
> > >
> > >>
> > >>>
> > >>> Regardless if added to the ring lib or as a separate lib,
> > >>> "reverse" APIs
> > >> (for single-
> > >>> thread use only) and zero-copy APIs can be added at any time later.
> > >
> > > As the only current use case for "reverse" (i.e. dequeue at tail,
> > > enqueue at
> > head) APIs is for the same-thread ring flavor, we could start by
> > adding only the specialized variants of the "reverse" APIs,
> > rte_ring_spc_reverse_xxx(), and initially omit the generic
> > rte_ring_reverse_xxx() APIs. (We need better names; I used "reverse"
> > for explanation only.)
> > >
  
Konstantin Ananyev Sept. 4, 2023, 10:13 a.m. UTC | #11
> Add a single thread safe and multi-thread unsafe ring data structure.
> This library provides an simple and efficient alternative to multi-thread
> safe ring when multi-thread safety is not required.

Just a thought: do we really need whole new library for that?
From what I understand all we need right now just one extra function:
rte_ring_mt_unsafe_prod_deque(...)
Sorry for ugly name :)
To dequeue N elems from prod.tail.
Or you think there would be some extra advantages in ST version of the ring:
extra usages, better performance, etc.?

> 
> Signed-off-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
> ---
> v1:
> 1) The code is very prelimnary and is not even compiled
> 2) This is intended to show the APIs and some thoughts on implementation
> 3) More APIs and the rest of the implementation will come in subsequent
>    versions
> 
>  lib/st_ring/rte_st_ring.h | 567 ++++++++++++++++++++++++++++++++++++++
>  1 file changed, 567 insertions(+)
>  create mode 100644 lib/st_ring/rte_st_ring.h
> 
> diff --git a/lib/st_ring/rte_st_ring.h b/lib/st_ring/rte_st_ring.h
> new file mode 100644
> index 0000000000..8cb8832591
> --- /dev/null
> +++ b/lib/st_ring/rte_st_ring.h
> @@ -0,0 +1,567 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2023 Arm Limited
> + */
> +
> +#ifndef _RTE_ST_RING_H_
> +#define _RTE_ST_RING_H_
> +
> +/**
> + * @file
> + * RTE Signle Thread Ring (ST Ring)
> + *
> + * The ST Ring is a fixed-size queue intended to be accessed
> + * by one thread at a time. It does not provide concurrent access to
> + * multiple threads. If there are multiple threads accessing the ST ring,
> + * then the threads have to use locks to protect the ring from
> + * getting corrupted.
> + *
> + * - FIFO (First In First Out)
> + * - Maximum size is fixed; the pointers are stored in a table.
> + * - Consumer and producer part of same thread.
> + * - Multi-thread producers and consumers need locking.
> + * - Single/Bulk/burst dequeue at Tail or Head
> + * - Single/Bulk/burst enqueue at Head or Tail
> + *
> + */
> +
> +#ifdef __cplusplus
> +extern "C" {
> +#endif
> +
> +#include <rte_st_ring_core.h>
> +#include <rte_st_ring_elem.h>
> +
> +/**
> + * Calculate the memory size needed for a ST ring
> + *
> + * This function returns the number of bytes needed for a ST ring, given
> + * the number of elements in it. This value is the sum of the size of
> + * the structure rte_st_ring and the size of the memory needed by the
> + * elements. The value is aligned to a cache line size.
> + *
> + * @param count
> + *   The number of elements in the ring (must be a power of 2).
> + * @return
> + *   - The memory size needed for the ST ring on success.
> + *   - -EINVAL if count is not a power of 2.
> + */
> +ssize_t rte_st_ring_get_memsize(unsigned int count);
> +
> +/**
> + * Initialize a ST ring structure.
> + *
> + * Initialize a ST ring structure in memory pointed by "r". The size of the
> + * memory area must be large enough to store the ring structure and the
> + * object table. It is advised to use rte_st_ring_get_memsize() to get the
> + * appropriate size.
> + *
> + * The ST ring size is set to *count*, which must be a power of two.
> + * The real usable ring size is *count-1* instead of *count* to
> + * differentiate a full ring from an empty ring.
> + *
> + * The ring is not added in RTE_TAILQ_ST_RING global list. Indeed, the
> + * memory given by the caller may not be shareable among dpdk
> + * processes.
> + *
> + * @param r
> + *   The pointer to the ring structure followed by the elements table.
> + * @param name
> + *   The name of the ring.
> + * @param count
> + *   The number of elements in the ring (must be a power of 2,
> + *   unless RTE_ST_RING_F_EXACT_SZ is set in flags).
> + * @param flags
> + *   An OR of the following:
> + *   - RTE_ST_RING_F_EXACT_SZ: If this flag is set, the ring will hold
> + *     exactly the requested number of entries, and the requested size
> + *     will be rounded up to the next power of two, but the usable space
> + *     will be exactly that requested. Worst case, if a power-of-2 size is
> + *     requested, half the ring space will be wasted.
> + *     Without this flag set, the ring size requested must be a power of 2,
> + *     and the usable space will be that size - 1.
> + * @return
> + *   0 on success, or a negative value on error.
> + */
> +int rte_st_ring_init(struct rte_st_ring *r, const char *name,
> +	unsigned int count, unsigned int flags);
> +
> +/**
> + * Create a new ST ring named *name* in memory.
> + *
> + * This function uses ``memzone_reserve()`` to allocate memory. Then it
> + * calls rte_st_ring_init() to initialize an empty ring.
> + *
> + * The new ring size is set to *count*, which must be a power of two.
> + * The real usable ring size is *count-1* instead of *count* to
> + * differentiate a full ring from an empty ring.
> + *
> + * The ring is added in RTE_TAILQ_ST_RING list.
> + *
> + * @param name
> + *   The name of the ring.
> + * @param count
> + *   The size of the ring (must be a power of 2,
> + *   unless RTE_ST_RING_F_EXACT_SZ is set in flags).
> + * @param socket_id
> + *   The *socket_id* argument is the socket identifier in case of
> + *   NUMA. The value can be *SOCKET_ID_ANY* if there is no NUMA
> + *   constraint for the reserved zone.
> + * @param flags
> + *   - RTE_ST_RING_F_EXACT_SZ: If this flag is set, the ring will hold exactly the
> + *     requested number of entries, and the requested size will be rounded up
> + *     to the next power of two, but the usable space will be exactly that
> + *     requested. Worst case, if a power-of-2 size is requested, half the
> + *     ring space will be wasted.
> + *     Without this flag set, the ring size requested must be a power of 2,
> + *     and the usable space will be that size - 1.
> + * @return
> + *   On success, the pointer to the new allocated ring. NULL on error with
> + *    rte_errno set appropriately. Possible errno values include:
> + *    - E_RTE_NO_CONFIG - function could not get pointer to rte_config structure
> + *    - EINVAL - count provided is not a power of 2
> + *    - ENOSPC - the maximum number of memzones has already been allocated
> + *    - EEXIST - a memzone with the same name already exists
> + *    - ENOMEM - no appropriate memory area found in which to create memzone
> + */
> +struct rte_st_ring *rte_st_ring_create(const char *name, unsigned int count,
> +				 int socket_id, unsigned int flags);
> +
> +/**
> + * De-allocate all memory used by the ring.
> + *
> + * @param r
> + *   Ring to free.
> + *   If NULL then, the function does nothing.
> + */
> +void rte_st_ring_free(struct rte_st_ring *r);
> +
> +/**
> + * Dump the status of the ring to a file.
> + *
> + * @param f
> + *   A pointer to a file for output
> + * @param r
> + *   A pointer to the ring structure.
> + */
> +void rte_st_ring_dump(FILE *f, const struct rte_st_ring *r);
> +
> +/**
> + * Enqueue fixed number of objects on a ST ring.
> + *
> + * This function copies the objects at the head of the ring and
> + * moves the head index.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_table
> + *   A pointer to a table of void * pointers (objects).
> + * @param n
> + *   The number of objects to add in the ring from the obj_table.
> + * @param free_space
> + *   if non-NULL, returns the amount of space in the ring after the
> + *   enqueue operation has finished.
> + * @return
> + *   The number of objects enqueued, either 0 or n
> + */
> +static __rte_always_inline unsigned int
> +rte_st_ring_enqueue_bulk(struct rte_st_ring *r, void * const *obj_table,
> +		      unsigned int n, unsigned int *free_space)
> +{
> +	return rte_st_ring_enqueue_bulk_elem(r, obj_table, sizeof(void *),
> +			n, free_space);
> +}
> +
> +/**
> + * Enqueue upto a maximum number of objects on a ST ring.
> + *
> + * This function copies the objects at the head of the ring and
> + * moves the head index.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_table
> + *   A pointer to a table of void * pointers (objects).
> + * @param n
> + *   The number of objects to add in the ring from the obj_table.
> + * @param free_space
> + *   if non-NULL, returns the amount of space in the ring after the
> + *   enqueue operation has finished.
> + * @return
> + *   - n: Actual number of objects enqueued.
> + */
> +static __rte_always_inline unsigned int
> +rte_st_ring_enqueue_burst(struct rte_st_ring *r, void * const *obj_table,
> +		      unsigned int n, unsigned int *free_space)
> +{
> +	return rte_st_ring_enqueue_burst_elem(r, obj_table, sizeof(void *),
> +			n, free_space);
> +}
> +
> +/**
> + * Enqueue one object on a ST ring.
> + *
> + * This function copies one object at the head of the ring and
> + * moves the head index.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj
> + *   A pointer to the object to be added.
> + * @return
> + *   - 0: Success; objects enqueued.
> + *   - -ENOBUFS: Not enough room in the ring to enqueue; no object is enqueued.
> + */
> +static __rte_always_inline int
> +rte_st_ring_enqueue(struct rte_st_ring *r, void *obj)
> +{
> +	return rte_st_ring_enqueue_elem(r, &obj, sizeof(void *));
> +}
> +
> +/**
> + * Enqueue fixed number of objects on a ST ring at the tail.
> + *
> + * This function copies the objects at the tail of the ring and
> + * moves the tail index (backwards).
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_table
> + *   A pointer to a table of void * pointers (objects).
> + * @param n
> + *   The number of objects to add in the ring from the obj_table.
> + * @param free_space
> + *   if non-NULL, returns the amount of space in the ring after the
> + *   enqueue operation has finished.
> + * @return
> + *   The number of objects enqueued, either 0 or n
> + */
> +static __rte_always_inline unsigned int
> +rte_st_ring_enqueue_at_tail_bulk(struct rte_st_ring *r,
> +				 void * const *obj_table, unsigned int n,
> +				 unsigned int *free_space)
> +{
> +	return rte_st_ring_enqueue_at_tail_bulk_elem(r, obj_table,
> +			sizeof(void *), n, free_space);
> +}
> +
> +/**
> + * Enqueue upto a maximum number of objects on a ST ring at the tail.
> + *
> + * This function copies the objects at the tail of the ring and
> + * moves the tail index (backwards).
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_table
> + *   A pointer to a table of void * pointers (objects).
> + * @param n
> + *   The number of objects to add in the ring from the obj_table.
> + * @param free_space
> + *   if non-NULL, returns the amount of space in the ring after the
> + *   enqueue operation has finished.
> + * @return
> + *   - n: Actual number of objects enqueued.
> + */
> +static __rte_always_inline unsigned int
> +rte_st_ring_enqueue_at_tail_burst(struct rte_st_ring *r,
> +				  void * const *obj_table, unsigned int n,
> +				  unsigned int *free_space)
> +{
> +	return rte_st_ring_enqueue_at_tail_burst_elem(r, obj_table,
> +			sizeof(void *), n, free_space);
> +}
> +
> +/**
> + * Enqueue one object on a ST ring at tail.
> + *
> + * This function copies one object at the tail of the ring and
> + * moves the tail index (backwards).
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj
> + *   A pointer to the object to be added.
> + * @return
> + *   - 0: Success; objects enqueued.
> + *   - -ENOBUFS: Not enough room in the ring to enqueue; no object is enqueued.
> + */
> +static __rte_always_inline int
> +rte_st_ring_enqueue_at_tail(struct rte_st_ring *r, void *obj)
> +{
> +	return rte_st_ring_enqueue_at_tail_elem(r, &obj, sizeof(void *));
> +}
> +
> +/**
> + * Dequeue a fixed number of objects from a ST ring.
> + *
> + * This function copies the objects from the tail of the ring and
> + * moves the tail index.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_table
> + *   A pointer to a table of void * pointers (objects) that will be filled.
> + * @param n
> + *   The number of objects to dequeue from the ring to the obj_table.
> + * @param available
> + *   If non-NULL, returns the number of remaining ring entries after the
> + *   dequeue has finished.
> + * @return
> + *   The number of objects dequeued, either 0 or n
> + */
> +static __rte_always_inline unsigned int
> +rte_st_ring_dequeue_bulk(struct rte_st_ring *r, void **obj_table, unsigned int n,
> +		unsigned int *available)
> +{
> +	return rte_st_ring_dequeue_bulk_elem(r, obj_table, sizeof(void *),
> +			n, available);
> +}
> +
> +/**
> + * Dequeue upto a maximum number of objects from a ST ring.
> + *
> + * This function copies the objects from the tail of the ring and
> + * moves the tail index.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_table
> + *   A pointer to a table of void * pointers (objects) that will be filled.
> + * @param n
> + *   The number of objects to dequeue from the ring to the obj_table.
> + * @param available
> + *   If non-NULL, returns the number of remaining ring entries after the
> + *   dequeue has finished.
> + * @return
> + *   - Number of objects dequeued
> + */
> +static __rte_always_inline unsigned int
> +rte_st_ring_dequeue_burst(struct rte_st_ring *r, void **obj_table,
> +		unsigned int n, unsigned int *available)
> +{
> +	return rte_st_ring_dequeue_burst_elem(r, obj_table, sizeof(void *),
> +			n, available);
> +}
> +
> +/**
> + * Dequeue one object from a ST ring.
> + *
> + * This function copies one object from the tail of the ring and
> + * moves the tail index.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_p
> + *   A pointer to a void * pointer (object) that will be filled.
> + * @return
> + *   - 0: Success, objects dequeued.
> + *   - -ENOENT: Not enough entries in the ring to dequeue, no object is
> + *     dequeued.
> + */
> +static __rte_always_inline int
> +rte_st_ring_dequeue(struct rte_st_ring *r, void **obj_p)
> +{
> +	return rte_st_ring_dequeue_elem(r, obj_p, sizeof(void *));
> +}
> +
> +/**
> + * Dequeue a fixed number of objects from a ST ring from the head.
> + *
> + * This function copies the objects from the head of the ring and
> + * moves the head index (backwards).
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_table
> + *   A pointer to a table of void * pointers (objects) that will be filled.
> + * @param n
> + *   The number of objects to dequeue from the ring to the obj_table.
> + * @param available
> + *   If non-NULL, returns the number of remaining ring entries after the
> + *   dequeue has finished.
> + * @return
> + *   The number of objects dequeued, either 0 or n
> + */
> +static __rte_always_inline unsigned int
> +rte_st_ring_dequeue_at_head_bulk(struct rte_st_ring *r, void **obj_table, unsigned int n,
> +		unsigned int *available)
> +{
> +	return rte_st_ring_dequeue_bulk_elem(r, obj_table, sizeof(void *),
> +			n, available);
> +}
> +
> +/**
> + * Dequeue upto a maximum number of objects from a ST ring from the head.
> + *
> + * This function copies the objects from the head of the ring and
> + * moves the head index (backwards).
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_table
> + *   A pointer to a table of void * pointers (objects) that will be filled.
> + * @param n
> + *   The number of objects to dequeue from the ring to the obj_table.
> + * @param available
> + *   If non-NULL, returns the number of remaining ring entries after the
> + *   dequeue has finished.
> + * @return
> + *   - Number of objects dequeued
> + */
> +static __rte_always_inline unsigned int
> +rte_st_ring_dequeue_at_head_burst(struct rte_st_ring *r, void **obj_table,
> +		unsigned int n, unsigned int *available)
> +{
> +	return rte_st_ring_dequeue_burst_elem(r, obj_table, sizeof(void *),
> +			n, available);
> +}
> +
> +/**
> + * Dequeue one object from a ST ring from the head.
> + *
> + * This function copies the objects from the head of the ring and
> + * moves the head index (backwards).
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @param obj_p
> + *   A pointer to a void * pointer (object) that will be filled.
> + * @return
> + *   - 0: Success, objects dequeued.
> + *   - -ENOENT: Not enough entries in the ring to dequeue, no object is
> + *     dequeued.
> + */
> +static __rte_always_inline int
> +rte_st_ring_at_head_dequeue(struct rte_st_ring *r, void **obj_p)
> +{
> +	return rte_st_ring_dequeue_elem(r, obj_p, sizeof(void *));
> +}
> +
> +/**
> + * Flush a ST ring.
> + *
> + * This function flush all the elements in a ST ring
> + *
> + * @warning
> + * Make sure the ring is not in use while calling this function.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + */
> +void
> +rte_st_ring_reset(struct rte_st_ring *r);
> +
> +/**
> + * Return the number of entries in a ST ring.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @return
> + *   The number of entries in the ring.
> + */
> +static inline unsigned int
> +rte_st_ring_count(const struct rte_st_ring *r)
> +{
> +	uint32_t count = (r->head - r->tail) & r->mask;
> +	return count;
> +}
> +
> +/**
> + * Return the number of free entries in a ST ring.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @return
> + *   The number of free entries in the ring.
> + */
> +static inline unsigned int
> +rte_st_ring_free_count(const struct rte_st_ring *r)
> +{
> +	return r->capacity - rte_st_ring_count(r);
> +}
> +
> +/**
> + * Test if a ST ring is full.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @return
> + *   - 1: The ring is full.
> + *   - 0: The ring is not full.
> + */
> +static inline int
> +rte_st_ring_full(const struct rte_st_ring *r)
> +{
> +	return rte_st_ring_free_count(r) == 0;
> +}
> +
> +/**
> + * Test if a ST ring is empty.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @return
> + *   - 1: The ring is empty.
> + *   - 0: The ring is not empty.
> + */
> +static inline int
> +rte_st_ring_empty(const struct rte_st_ring *r)
> +{
> +	return r->tail == r->head;
> +}
> +
> +/**
> + * Return the size of the ring.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @return
> + *   The size of the data store used by the ring.
> + *   NOTE: this is not the same as the usable space in the ring. To query that
> + *   use ``rte_st_ring_get_capacity()``.
> + */
> +static inline unsigned int
> +rte_st_ring_get_size(const struct rte_st_ring *r)
> +{
> +	return r->size;
> +}
> +
> +/**
> + * Return the number of elements which can be stored in the ring.
> + *
> + * @param r
> + *   A pointer to the ring structure.
> + * @return
> + *   The usable size of the ring.
> + */
> +static inline unsigned int
> +rte_st_ring_get_capacity(const struct rte_st_ring *r)
> +{
> +	return r->capacity;
> +}
> +
> +/**
> + * Dump the status of all rings on the console
> + *
> + * @param f
> + *   A pointer to a file for output
> + */
> +void rte_st_ring_list_dump(FILE *f);
> +
> +/**
> + * Search a ST ring from its name
> + *
> + * @param name
> + *   The name of the ring.
> + * @return
> + *   The pointer to the ring matching the name, or NULL if not found,
> + *   with rte_errno set appropriately. Possible rte_errno values include:
> + *    - ENOENT - required entry not available to return.
> + */
> +struct rte_st_ring *rte_st_ring_lookup(const char *name);
> +
> +#ifdef __cplusplus
> +}
> +#endif
> +
> +#endif /* _RTE_ST_RING_H_ */
> --
> 2.25.1
>
  
Honnappa Nagarahalli Sept. 4, 2023, 6:10 p.m. UTC | #12
> -----Original Message-----
> From: Konstantin Ananyev <konstantin.ananyev@huawei.com>
> Sent: Monday, September 4, 2023 5:13 AM
> To: Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com>;
> jackmin@nvidia.com; konstantin.v.ananyev@yandex.ru
> Cc: dev@dpdk.org; Ruifeng Wang <Ruifeng.Wang@arm.com>; Aditya
> Ambadipudi <Aditya.Ambadipudi@arm.com>; Wathsala Wathawana Vithanage
> <wathsala.vithanage@arm.com>; nd <nd@arm.com>
> Subject: RE: [RFC] lib/st_ring: add single thread ring
> 
> 
> 
> > Add a single thread safe and multi-thread unsafe ring data structure.
> > This library provides an simple and efficient alternative to
> > multi-thread safe ring when multi-thread safety is not required.
> 
> Just a thought: do we really need whole new library for that?
> From what I understand all we need right now just one extra function:
> rte_ring_mt_unsafe_prod_deque(...)
> Sorry for ugly name :)
> To dequeue N elems from prod.tail.
> Or you think there would be some extra advantages in ST version of the ring:
> extra usages, better performance, etc.?
There are multiple implementations of the ST ring being used in other parts of DPDK. Mattias Ronnblom pointed out some (distributed scheduler, eth RX adapter, cmdline) [1] existing ones which will be replaced by this one.
This implementation will not use atomic instructions, head and tail indices will be in the same cache line and it will be a double ended queue. So, I am expecting better perf and more use cases (some might not be applicable currently).

[1] https://mails.dpdk.org/archives/dev/2023-August/275003.html

> 
> >
> > Signed-off-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
> > ---
> > v1:
> > 1) The code is very prelimnary and is not even compiled
> > 2) This is intended to show the APIs and some thoughts on
> > implementation
> > 3) More APIs and the rest of the implementation will come in subsequent
> >    versions
> >
> >  lib/st_ring/rte_st_ring.h | 567
> > ++++++++++++++++++++++++++++++++++++++
> >  1 file changed, 567 insertions(+)
> >  create mode 100644 lib/st_ring/rte_st_ring.h
> >
> > diff --git a/lib/st_ring/rte_st_ring.h b/lib/st_ring/rte_st_ring.h new
> > file mode 100644 index 0000000000..8cb8832591
> > --- /dev/null
> > +++ b/lib/st_ring/rte_st_ring.h
> > @@ -0,0 +1,567 @@
> > +/* SPDX-License-Identifier: BSD-3-Clause
> > + * Copyright(c) 2023 Arm Limited
> > + */
> > +
> > +#ifndef _RTE_ST_RING_H_
> > +#define _RTE_ST_RING_H_
> > +
> > +/**
> > + * @file
> > + * RTE Signle Thread Ring (ST Ring)
> > + *
> > + * The ST Ring is a fixed-size queue intended to be accessed
> > + * by one thread at a time. It does not provide concurrent access to
> > + * multiple threads. If there are multiple threads accessing the ST
> > +ring,
> > + * then the threads have to use locks to protect the ring from
> > + * getting corrupted.
> > + *
> > + * - FIFO (First In First Out)
> > + * - Maximum size is fixed; the pointers are stored in a table.
> > + * - Consumer and producer part of same thread.
> > + * - Multi-thread producers and consumers need locking.
> > + * - Single/Bulk/burst dequeue at Tail or Head
> > + * - Single/Bulk/burst enqueue at Head or Tail
> > + *
> > + */
> > +
> > +#ifdef __cplusplus
> > +extern "C" {
> > +#endif
> > +
> > +#include <rte_st_ring_core.h>
> > +#include <rte_st_ring_elem.h>
> > +
> > +/**
> > + * Calculate the memory size needed for a ST ring
> > + *
> > + * This function returns the number of bytes needed for a ST ring,
> > +given
> > + * the number of elements in it. This value is the sum of the size of
> > + * the structure rte_st_ring and the size of the memory needed by the
> > + * elements. The value is aligned to a cache line size.
> > + *
> > + * @param count
> > + *   The number of elements in the ring (must be a power of 2).
> > + * @return
> > + *   - The memory size needed for the ST ring on success.
> > + *   - -EINVAL if count is not a power of 2.
> > + */
> > +ssize_t rte_st_ring_get_memsize(unsigned int count);
> > +
> > +/**
> > + * Initialize a ST ring structure.
> > + *
> > + * Initialize a ST ring structure in memory pointed by "r". The size
> > +of the
> > + * memory area must be large enough to store the ring structure and
> > +the
> > + * object table. It is advised to use rte_st_ring_get_memsize() to
> > +get the
> > + * appropriate size.
> > + *
> > + * The ST ring size is set to *count*, which must be a power of two.
> > + * The real usable ring size is *count-1* instead of *count* to
> > + * differentiate a full ring from an empty ring.
> > + *
> > + * The ring is not added in RTE_TAILQ_ST_RING global list. Indeed,
> > +the
> > + * memory given by the caller may not be shareable among dpdk
> > + * processes.
> > + *
> > + * @param r
> > + *   The pointer to the ring structure followed by the elements table.
> > + * @param name
> > + *   The name of the ring.
> > + * @param count
> > + *   The number of elements in the ring (must be a power of 2,
> > + *   unless RTE_ST_RING_F_EXACT_SZ is set in flags).
> > + * @param flags
> > + *   An OR of the following:
> > + *   - RTE_ST_RING_F_EXACT_SZ: If this flag is set, the ring will hold
> > + *     exactly the requested number of entries, and the requested size
> > + *     will be rounded up to the next power of two, but the usable space
> > + *     will be exactly that requested. Worst case, if a power-of-2 size is
> > + *     requested, half the ring space will be wasted.
> > + *     Without this flag set, the ring size requested must be a power of 2,
> > + *     and the usable space will be that size - 1.
> > + * @return
> > + *   0 on success, or a negative value on error.
> > + */
> > +int rte_st_ring_init(struct rte_st_ring *r, const char *name,
> > +	unsigned int count, unsigned int flags);
> > +
> > +/**
> > + * Create a new ST ring named *name* in memory.
> > + *
> > + * This function uses ``memzone_reserve()`` to allocate memory. Then
> > +it
> > + * calls rte_st_ring_init() to initialize an empty ring.
> > + *
> > + * The new ring size is set to *count*, which must be a power of two.
> > + * The real usable ring size is *count-1* instead of *count* to
> > + * differentiate a full ring from an empty ring.
> > + *
> > + * The ring is added in RTE_TAILQ_ST_RING list.
> > + *
> > + * @param name
> > + *   The name of the ring.
> > + * @param count
> > + *   The size of the ring (must be a power of 2,
> > + *   unless RTE_ST_RING_F_EXACT_SZ is set in flags).
> > + * @param socket_id
> > + *   The *socket_id* argument is the socket identifier in case of
> > + *   NUMA. The value can be *SOCKET_ID_ANY* if there is no NUMA
> > + *   constraint for the reserved zone.
> > + * @param flags
> > + *   - RTE_ST_RING_F_EXACT_SZ: If this flag is set, the ring will hold exactly
> the
> > + *     requested number of entries, and the requested size will be rounded up
> > + *     to the next power of two, but the usable space will be exactly that
> > + *     requested. Worst case, if a power-of-2 size is requested, half the
> > + *     ring space will be wasted.
> > + *     Without this flag set, the ring size requested must be a power of 2,
> > + *     and the usable space will be that size - 1.
> > + * @return
> > + *   On success, the pointer to the new allocated ring. NULL on error with
> > + *    rte_errno set appropriately. Possible errno values include:
> > + *    - E_RTE_NO_CONFIG - function could not get pointer to rte_config
> structure
> > + *    - EINVAL - count provided is not a power of 2
> > + *    - ENOSPC - the maximum number of memzones has already been
> allocated
> > + *    - EEXIST - a memzone with the same name already exists
> > + *    - ENOMEM - no appropriate memory area found in which to create
> memzone
> > + */
> > +struct rte_st_ring *rte_st_ring_create(const char *name, unsigned int count,
> > +				 int socket_id, unsigned int flags);
> > +
> > +/**
> > + * De-allocate all memory used by the ring.
> > + *
> > + * @param r
> > + *   Ring to free.
> > + *   If NULL then, the function does nothing.
> > + */
> > +void rte_st_ring_free(struct rte_st_ring *r);
> > +
> > +/**
> > + * Dump the status of the ring to a file.
> > + *
> > + * @param f
> > + *   A pointer to a file for output
> > + * @param r
> > + *   A pointer to the ring structure.
> > + */
> > +void rte_st_ring_dump(FILE *f, const struct rte_st_ring *r);
> > +
> > +/**
> > + * Enqueue fixed number of objects on a ST ring.
> > + *
> > + * This function copies the objects at the head of the ring and
> > + * moves the head index.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_table
> > + *   A pointer to a table of void * pointers (objects).
> > + * @param n
> > + *   The number of objects to add in the ring from the obj_table.
> > + * @param free_space
> > + *   if non-NULL, returns the amount of space in the ring after the
> > + *   enqueue operation has finished.
> > + * @return
> > + *   The number of objects enqueued, either 0 or n
> > + */
> > +static __rte_always_inline unsigned int
> > +rte_st_ring_enqueue_bulk(struct rte_st_ring *r, void * const *obj_table,
> > +		      unsigned int n, unsigned int *free_space) {
> > +	return rte_st_ring_enqueue_bulk_elem(r, obj_table, sizeof(void *),
> > +			n, free_space);
> > +}
> > +
> > +/**
> > + * Enqueue upto a maximum number of objects on a ST ring.
> > + *
> > + * This function copies the objects at the head of the ring and
> > + * moves the head index.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_table
> > + *   A pointer to a table of void * pointers (objects).
> > + * @param n
> > + *   The number of objects to add in the ring from the obj_table.
> > + * @param free_space
> > + *   if non-NULL, returns the amount of space in the ring after the
> > + *   enqueue operation has finished.
> > + * @return
> > + *   - n: Actual number of objects enqueued.
> > + */
> > +static __rte_always_inline unsigned int
> > +rte_st_ring_enqueue_burst(struct rte_st_ring *r, void * const *obj_table,
> > +		      unsigned int n, unsigned int *free_space) {
> > +	return rte_st_ring_enqueue_burst_elem(r, obj_table, sizeof(void *),
> > +			n, free_space);
> > +}
> > +
> > +/**
> > + * Enqueue one object on a ST ring.
> > + *
> > + * This function copies one object at the head of the ring and
> > + * moves the head index.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj
> > + *   A pointer to the object to be added.
> > + * @return
> > + *   - 0: Success; objects enqueued.
> > + *   - -ENOBUFS: Not enough room in the ring to enqueue; no object is
> enqueued.
> > + */
> > +static __rte_always_inline int
> > +rte_st_ring_enqueue(struct rte_st_ring *r, void *obj) {
> > +	return rte_st_ring_enqueue_elem(r, &obj, sizeof(void *)); }
> > +
> > +/**
> > + * Enqueue fixed number of objects on a ST ring at the tail.
> > + *
> > + * This function copies the objects at the tail of the ring and
> > + * moves the tail index (backwards).
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_table
> > + *   A pointer to a table of void * pointers (objects).
> > + * @param n
> > + *   The number of objects to add in the ring from the obj_table.
> > + * @param free_space
> > + *   if non-NULL, returns the amount of space in the ring after the
> > + *   enqueue operation has finished.
> > + * @return
> > + *   The number of objects enqueued, either 0 or n
> > + */
> > +static __rte_always_inline unsigned int
> > +rte_st_ring_enqueue_at_tail_bulk(struct rte_st_ring *r,
> > +				 void * const *obj_table, unsigned int n,
> > +				 unsigned int *free_space)
> > +{
> > +	return rte_st_ring_enqueue_at_tail_bulk_elem(r, obj_table,
> > +			sizeof(void *), n, free_space);
> > +}
> > +
> > +/**
> > + * Enqueue upto a maximum number of objects on a ST ring at the tail.
> > + *
> > + * This function copies the objects at the tail of the ring and
> > + * moves the tail index (backwards).
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_table
> > + *   A pointer to a table of void * pointers (objects).
> > + * @param n
> > + *   The number of objects to add in the ring from the obj_table.
> > + * @param free_space
> > + *   if non-NULL, returns the amount of space in the ring after the
> > + *   enqueue operation has finished.
> > + * @return
> > + *   - n: Actual number of objects enqueued.
> > + */
> > +static __rte_always_inline unsigned int
> > +rte_st_ring_enqueue_at_tail_burst(struct rte_st_ring *r,
> > +				  void * const *obj_table, unsigned int n,
> > +				  unsigned int *free_space)
> > +{
> > +	return rte_st_ring_enqueue_at_tail_burst_elem(r, obj_table,
> > +			sizeof(void *), n, free_space);
> > +}
> > +
> > +/**
> > + * Enqueue one object on a ST ring at tail.
> > + *
> > + * This function copies one object at the tail of the ring and
> > + * moves the tail index (backwards).
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj
> > + *   A pointer to the object to be added.
> > + * @return
> > + *   - 0: Success; objects enqueued.
> > + *   - -ENOBUFS: Not enough room in the ring to enqueue; no object is
> enqueued.
> > + */
> > +static __rte_always_inline int
> > +rte_st_ring_enqueue_at_tail(struct rte_st_ring *r, void *obj) {
> > +	return rte_st_ring_enqueue_at_tail_elem(r, &obj, sizeof(void *)); }
> > +
> > +/**
> > + * Dequeue a fixed number of objects from a ST ring.
> > + *
> > + * This function copies the objects from the tail of the ring and
> > + * moves the tail index.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_table
> > + *   A pointer to a table of void * pointers (objects) that will be filled.
> > + * @param n
> > + *   The number of objects to dequeue from the ring to the obj_table.
> > + * @param available
> > + *   If non-NULL, returns the number of remaining ring entries after the
> > + *   dequeue has finished.
> > + * @return
> > + *   The number of objects dequeued, either 0 or n
> > + */
> > +static __rte_always_inline unsigned int
> > +rte_st_ring_dequeue_bulk(struct rte_st_ring *r, void **obj_table, unsigned
> int n,
> > +		unsigned int *available)
> > +{
> > +	return rte_st_ring_dequeue_bulk_elem(r, obj_table, sizeof(void *),
> > +			n, available);
> > +}
> > +
> > +/**
> > + * Dequeue upto a maximum number of objects from a ST ring.
> > + *
> > + * This function copies the objects from the tail of the ring and
> > + * moves the tail index.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_table
> > + *   A pointer to a table of void * pointers (objects) that will be filled.
> > + * @param n
> > + *   The number of objects to dequeue from the ring to the obj_table.
> > + * @param available
> > + *   If non-NULL, returns the number of remaining ring entries after the
> > + *   dequeue has finished.
> > + * @return
> > + *   - Number of objects dequeued
> > + */
> > +static __rte_always_inline unsigned int
> > +rte_st_ring_dequeue_burst(struct rte_st_ring *r, void **obj_table,
> > +		unsigned int n, unsigned int *available) {
> > +	return rte_st_ring_dequeue_burst_elem(r, obj_table, sizeof(void *),
> > +			n, available);
> > +}
> > +
> > +/**
> > + * Dequeue one object from a ST ring.
> > + *
> > + * This function copies one object from the tail of the ring and
> > + * moves the tail index.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_p
> > + *   A pointer to a void * pointer (object) that will be filled.
> > + * @return
> > + *   - 0: Success, objects dequeued.
> > + *   - -ENOENT: Not enough entries in the ring to dequeue, no object is
> > + *     dequeued.
> > + */
> > +static __rte_always_inline int
> > +rte_st_ring_dequeue(struct rte_st_ring *r, void **obj_p) {
> > +	return rte_st_ring_dequeue_elem(r, obj_p, sizeof(void *)); }
> > +
> > +/**
> > + * Dequeue a fixed number of objects from a ST ring from the head.
> > + *
> > + * This function copies the objects from the head of the ring and
> > + * moves the head index (backwards).
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_table
> > + *   A pointer to a table of void * pointers (objects) that will be filled.
> > + * @param n
> > + *   The number of objects to dequeue from the ring to the obj_table.
> > + * @param available
> > + *   If non-NULL, returns the number of remaining ring entries after the
> > + *   dequeue has finished.
> > + * @return
> > + *   The number of objects dequeued, either 0 or n
> > + */
> > +static __rte_always_inline unsigned int
> > +rte_st_ring_dequeue_at_head_bulk(struct rte_st_ring *r, void **obj_table,
> unsigned int n,
> > +		unsigned int *available)
> > +{
> > +	return rte_st_ring_dequeue_bulk_elem(r, obj_table, sizeof(void *),
> > +			n, available);
> > +}
> > +
> > +/**
> > + * Dequeue upto a maximum number of objects from a ST ring from the
> head.
> > + *
> > + * This function copies the objects from the head of the ring and
> > + * moves the head index (backwards).
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_table
> > + *   A pointer to a table of void * pointers (objects) that will be filled.
> > + * @param n
> > + *   The number of objects to dequeue from the ring to the obj_table.
> > + * @param available
> > + *   If non-NULL, returns the number of remaining ring entries after the
> > + *   dequeue has finished.
> > + * @return
> > + *   - Number of objects dequeued
> > + */
> > +static __rte_always_inline unsigned int
> > +rte_st_ring_dequeue_at_head_burst(struct rte_st_ring *r, void
> **obj_table,
> > +		unsigned int n, unsigned int *available) {
> > +	return rte_st_ring_dequeue_burst_elem(r, obj_table, sizeof(void *),
> > +			n, available);
> > +}
> > +
> > +/**
> > + * Dequeue one object from a ST ring from the head.
> > + *
> > + * This function copies the objects from the head of the ring and
> > + * moves the head index (backwards).
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @param obj_p
> > + *   A pointer to a void * pointer (object) that will be filled.
> > + * @return
> > + *   - 0: Success, objects dequeued.
> > + *   - -ENOENT: Not enough entries in the ring to dequeue, no object is
> > + *     dequeued.
> > + */
> > +static __rte_always_inline int
> > +rte_st_ring_at_head_dequeue(struct rte_st_ring *r, void **obj_p) {
> > +	return rte_st_ring_dequeue_elem(r, obj_p, sizeof(void *)); }
> > +
> > +/**
> > + * Flush a ST ring.
> > + *
> > + * This function flush all the elements in a ST ring
> > + *
> > + * @warning
> > + * Make sure the ring is not in use while calling this function.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + */
> > +void
> > +rte_st_ring_reset(struct rte_st_ring *r);
> > +
> > +/**
> > + * Return the number of entries in a ST ring.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @return
> > + *   The number of entries in the ring.
> > + */
> > +static inline unsigned int
> > +rte_st_ring_count(const struct rte_st_ring *r) {
> > +	uint32_t count = (r->head - r->tail) & r->mask;
> > +	return count;
> > +}
> > +
> > +/**
> > + * Return the number of free entries in a ST ring.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @return
> > + *   The number of free entries in the ring.
> > + */
> > +static inline unsigned int
> > +rte_st_ring_free_count(const struct rte_st_ring *r) {
> > +	return r->capacity - rte_st_ring_count(r); }
> > +
> > +/**
> > + * Test if a ST ring is full.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @return
> > + *   - 1: The ring is full.
> > + *   - 0: The ring is not full.
> > + */
> > +static inline int
> > +rte_st_ring_full(const struct rte_st_ring *r) {
> > +	return rte_st_ring_free_count(r) == 0; }
> > +
> > +/**
> > + * Test if a ST ring is empty.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @return
> > + *   - 1: The ring is empty.
> > + *   - 0: The ring is not empty.
> > + */
> > +static inline int
> > +rte_st_ring_empty(const struct rte_st_ring *r) {
> > +	return r->tail == r->head;
> > +}
> > +
> > +/**
> > + * Return the size of the ring.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @return
> > + *   The size of the data store used by the ring.
> > + *   NOTE: this is not the same as the usable space in the ring. To query that
> > + *   use ``rte_st_ring_get_capacity()``.
> > + */
> > +static inline unsigned int
> > +rte_st_ring_get_size(const struct rte_st_ring *r) {
> > +	return r->size;
> > +}
> > +
> > +/**
> > + * Return the number of elements which can be stored in the ring.
> > + *
> > + * @param r
> > + *   A pointer to the ring structure.
> > + * @return
> > + *   The usable size of the ring.
> > + */
> > +static inline unsigned int
> > +rte_st_ring_get_capacity(const struct rte_st_ring *r) {
> > +	return r->capacity;
> > +}
> > +
> > +/**
> > + * Dump the status of all rings on the console
> > + *
> > + * @param f
> > + *   A pointer to a file for output
> > + */
> > +void rte_st_ring_list_dump(FILE *f);
> > +
> > +/**
> > + * Search a ST ring from its name
> > + *
> > + * @param name
> > + *   The name of the ring.
> > + * @return
> > + *   The pointer to the ring matching the name, or NULL if not found,
> > + *   with rte_errno set appropriately. Possible rte_errno values include:
> > + *    - ENOENT - required entry not available to return.
> > + */
> > +struct rte_st_ring *rte_st_ring_lookup(const char *name);
> > +
> > +#ifdef __cplusplus
> > +}
> > +#endif
> > +
> > +#endif /* _RTE_ST_RING_H_ */
> > --
> > 2.25.1
> >
  
Konstantin Ananyev Sept. 5, 2023, 8:19 a.m. UTC | #13
> > > Add a single thread safe and multi-thread unsafe ring data structure.
> > > This library provides an simple and efficient alternative to
> > > multi-thread safe ring when multi-thread safety is not required.
> >
> > Just a thought: do we really need whole new library for that?
> > From what I understand all we need right now just one extra function:
> > rte_ring_mt_unsafe_prod_deque(...)
> > Sorry for ugly name :)
> > To dequeue N elems from prod.tail.
> > Or you think there would be some extra advantages in ST version of the ring:
> > extra usages, better performance, etc.?
> There are multiple implementations of the ST ring being used in other parts of DPDK. Mattias Ronnblom pointed out some (distributed
> scheduler, eth RX adapter, cmdline) [1] existing ones which will be replaced by this one.
> This implementation will not use atomic instructions, head and tail indices will be in the same cache line and it will be a double ended
> queue. So, I am expecting better perf and more use cases (some might not be applicable currently).

Yep, I do understand that we can skip sync logic for ST case.
Ok, if we do have multiple use-cases it might be plausible to have a separate API for it.

> 
> [1] https://mails.dpdk.org/archives/dev/2023-August/275003.html
> 
> >
> > >
> > > Signed-off-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
> > > ---
> > > v1:
> > > 1) The code is very prelimnary and is not even compiled
> > > 2) This is intended to show the APIs and some thoughts on
> > > implementation
> > > 3) More APIs and the rest of the implementation will come in subsequent
> > >    versions
> > >
> > >  lib/st_ring/rte_st_ring.h | 567
> > > ++++++++++++++++++++++++++++++++++++++
> > >  1 file changed, 567 insertions(+)
> > >  create mode 100644 lib/st_ring/rte_st_ring.h
> > >
> > > diff --git a/lib/st_ring/rte_st_ring.h b/lib/st_ring/rte_st_ring.h new
> > > file mode 100644 index 0000000000..8cb8832591
> > > --- /dev/null
> > > +++ b/lib/st_ring/rte_st_ring.h
> > > @@ -0,0 +1,567 @@
> > > +/* SPDX-License-Identifier: BSD-3-Clause
> > > + * Copyright(c) 2023 Arm Limited
> > > + */
> > > +
> > > +#ifndef _RTE_ST_RING_H_
> > > +#define _RTE_ST_RING_H_
> > > +
> > > +/**
> > > + * @file
> > > + * RTE Signle Thread Ring (ST Ring)
> > > + *
> > > + * The ST Ring is a fixed-size queue intended to be accessed
> > > + * by one thread at a time. It does not provide concurrent access to
> > > + * multiple threads. If there are multiple threads accessing the ST
> > > +ring,
> > > + * then the threads have to use locks to protect the ring from
> > > + * getting corrupted.
> > > + *
> > > + * - FIFO (First In First Out)
> > > + * - Maximum size is fixed; the pointers are stored in a table.
> > > + * - Consumer and producer part of same thread.
> > > + * - Multi-thread producers and consumers need locking.
> > > + * - Single/Bulk/burst dequeue at Tail or Head
> > > + * - Single/Bulk/burst enqueue at Head or Tail
> > > + *
> > > + */
> > > +
> > > +#ifdef __cplusplus
> > > +extern "C" {
> > > +#endif
> > > +
> > > +#include <rte_st_ring_core.h>
> > > +#include <rte_st_ring_elem.h>
> > > +
> > > +/**
> > > + * Calculate the memory size needed for a ST ring
> > > + *
> > > + * This function returns the number of bytes needed for a ST ring,
> > > +given
> > > + * the number of elements in it. This value is the sum of the size of
> > > + * the structure rte_st_ring and the size of the memory needed by the
> > > + * elements. The value is aligned to a cache line size.
> > > + *
> > > + * @param count
> > > + *   The number of elements in the ring (must be a power of 2).
> > > + * @return
> > > + *   - The memory size needed for the ST ring on success.
> > > + *   - -EINVAL if count is not a power of 2.
> > > + */
> > > +ssize_t rte_st_ring_get_memsize(unsigned int count);
> > > +
> > > +/**
> > > + * Initialize a ST ring structure.
> > > + *
> > > + * Initialize a ST ring structure in memory pointed by "r". The size
> > > +of the
> > > + * memory area must be large enough to store the ring structure and
> > > +the
> > > + * object table. It is advised to use rte_st_ring_get_memsize() to
> > > +get the
> > > + * appropriate size.
> > > + *
> > > + * The ST ring size is set to *count*, which must be a power of two.
> > > + * The real usable ring size is *count-1* instead of *count* to
> > > + * differentiate a full ring from an empty ring.
> > > + *
> > > + * The ring is not added in RTE_TAILQ_ST_RING global list. Indeed,
> > > +the
> > > + * memory given by the caller may not be shareable among dpdk
> > > + * processes.
> > > + *
> > > + * @param r
> > > + *   The pointer to the ring structure followed by the elements table.
> > > + * @param name
> > > + *   The name of the ring.
> > > + * @param count
> > > + *   The number of elements in the ring (must be a power of 2,
> > > + *   unless RTE_ST_RING_F_EXACT_SZ is set in flags).
> > > + * @param flags
> > > + *   An OR of the following:
> > > + *   - RTE_ST_RING_F_EXACT_SZ: If this flag is set, the ring will hold
> > > + *     exactly the requested number of entries, and the requested size
> > > + *     will be rounded up to the next power of two, but the usable space
> > > + *     will be exactly that requested. Worst case, if a power-of-2 size is
> > > + *     requested, half the ring space will be wasted.
> > > + *     Without this flag set, the ring size requested must be a power of 2,
> > > + *     and the usable space will be that size - 1.
> > > + * @return
> > > + *   0 on success, or a negative value on error.
> > > + */
> > > +int rte_st_ring_init(struct rte_st_ring *r, const char *name,
> > > +	unsigned int count, unsigned int flags);
> > > +
> > > +/**
> > > + * Create a new ST ring named *name* in memory.
> > > + *
> > > + * This function uses ``memzone_reserve()`` to allocate memory. Then
> > > +it
> > > + * calls rte_st_ring_init() to initialize an empty ring.
> > > + *
> > > + * The new ring size is set to *count*, which must be a power of two.
> > > + * The real usable ring size is *count-1* instead of *count* to
> > > + * differentiate a full ring from an empty ring.
> > > + *
> > > + * The ring is added in RTE_TAILQ_ST_RING list.
> > > + *
> > > + * @param name
> > > + *   The name of the ring.
> > > + * @param count
> > > + *   The size of the ring (must be a power of 2,
> > > + *   unless RTE_ST_RING_F_EXACT_SZ is set in flags).
> > > + * @param socket_id
> > > + *   The *socket_id* argument is the socket identifier in case of
> > > + *   NUMA. The value can be *SOCKET_ID_ANY* if there is no NUMA
> > > + *   constraint for the reserved zone.
> > > + * @param flags
> > > + *   - RTE_ST_RING_F_EXACT_SZ: If this flag is set, the ring will hold exactly
> > the
> > > + *     requested number of entries, and the requested size will be rounded up
> > > + *     to the next power of two, but the usable space will be exactly that
> > > + *     requested. Worst case, if a power-of-2 size is requested, half the
> > > + *     ring space will be wasted.
> > > + *     Without this flag set, the ring size requested must be a power of 2,
> > > + *     and the usable space will be that size - 1.
> > > + * @return
> > > + *   On success, the pointer to the new allocated ring. NULL on error with
> > > + *    rte_errno set appropriately. Possible errno values include:
> > > + *    - E_RTE_NO_CONFIG - function could not get pointer to rte_config
> > structure
> > > + *    - EINVAL - count provided is not a power of 2
> > > + *    - ENOSPC - the maximum number of memzones has already been
> > allocated
> > > + *    - EEXIST - a memzone with the same name already exists
> > > + *    - ENOMEM - no appropriate memory area found in which to create
> > memzone
> > > + */
> > > +struct rte_st_ring *rte_st_ring_create(const char *name, unsigned int count,
> > > +				 int socket_id, unsigned int flags);
> > > +
> > > +/**
> > > + * De-allocate all memory used by the ring.
> > > + *
> > > + * @param r
> > > + *   Ring to free.
> > > + *   If NULL then, the function does nothing.
> > > + */
> > > +void rte_st_ring_free(struct rte_st_ring *r);
> > > +
> > > +/**
> > > + * Dump the status of the ring to a file.
> > > + *
> > > + * @param f
> > > + *   A pointer to a file for output
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + */
> > > +void rte_st_ring_dump(FILE *f, const struct rte_st_ring *r);
> > > +
> > > +/**
> > > + * Enqueue fixed number of objects on a ST ring.
> > > + *
> > > + * This function copies the objects at the head of the ring and
> > > + * moves the head index.
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @param obj_table
> > > + *   A pointer to a table of void * pointers (objects).
> > > + * @param n
> > > + *   The number of objects to add in the ring from the obj_table.
> > > + * @param free_space
> > > + *   if non-NULL, returns the amount of space in the ring after the
> > > + *   enqueue operation has finished.
> > > + * @return
> > > + *   The number of objects enqueued, either 0 or n
> > > + */
> > > +static __rte_always_inline unsigned int
> > > +rte_st_ring_enqueue_bulk(struct rte_st_ring *r, void * const *obj_table,
> > > +		      unsigned int n, unsigned int *free_space) {
> > > +	return rte_st_ring_enqueue_bulk_elem(r, obj_table, sizeof(void *),
> > > +			n, free_space);
> > > +}
> > > +
> > > +/**
> > > + * Enqueue upto a maximum number of objects on a ST ring.
> > > + *
> > > + * This function copies the objects at the head of the ring and
> > > + * moves the head index.
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @param obj_table
> > > + *   A pointer to a table of void * pointers (objects).
> > > + * @param n
> > > + *   The number of objects to add in the ring from the obj_table.
> > > + * @param free_space
> > > + *   if non-NULL, returns the amount of space in the ring after the
> > > + *   enqueue operation has finished.
> > > + * @return
> > > + *   - n: Actual number of objects enqueued.
> > > + */
> > > +static __rte_always_inline unsigned int
> > > +rte_st_ring_enqueue_burst(struct rte_st_ring *r, void * const *obj_table,
> > > +		      unsigned int n, unsigned int *free_space) {
> > > +	return rte_st_ring_enqueue_burst_elem(r, obj_table, sizeof(void *),
> > > +			n, free_space);
> > > +}
> > > +
> > > +/**
> > > + * Enqueue one object on a ST ring.
> > > + *
> > > + * This function copies one object at the head of the ring and
> > > + * moves the head index.
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @param obj
> > > + *   A pointer to the object to be added.
> > > + * @return
> > > + *   - 0: Success; objects enqueued.
> > > + *   - -ENOBUFS: Not enough room in the ring to enqueue; no object is
> > enqueued.
> > > + */
> > > +static __rte_always_inline int
> > > +rte_st_ring_enqueue(struct rte_st_ring *r, void *obj) {
> > > +	return rte_st_ring_enqueue_elem(r, &obj, sizeof(void *)); }
> > > +
> > > +/**
> > > + * Enqueue fixed number of objects on a ST ring at the tail.
> > > + *
> > > + * This function copies the objects at the tail of the ring and
> > > + * moves the tail index (backwards).
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @param obj_table
> > > + *   A pointer to a table of void * pointers (objects).
> > > + * @param n
> > > + *   The number of objects to add in the ring from the obj_table.
> > > + * @param free_space
> > > + *   if non-NULL, returns the amount of space in the ring after the
> > > + *   enqueue operation has finished.
> > > + * @return
> > > + *   The number of objects enqueued, either 0 or n
> > > + */
> > > +static __rte_always_inline unsigned int
> > > +rte_st_ring_enqueue_at_tail_bulk(struct rte_st_ring *r,
> > > +				 void * const *obj_table, unsigned int n,
> > > +				 unsigned int *free_space)
> > > +{
> > > +	return rte_st_ring_enqueue_at_tail_bulk_elem(r, obj_table,
> > > +			sizeof(void *), n, free_space);
> > > +}
> > > +
> > > +/**
> > > + * Enqueue upto a maximum number of objects on a ST ring at the tail.
> > > + *
> > > + * This function copies the objects at the tail of the ring and
> > > + * moves the tail index (backwards).
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @param obj_table
> > > + *   A pointer to a table of void * pointers (objects).
> > > + * @param n
> > > + *   The number of objects to add in the ring from the obj_table.
> > > + * @param free_space
> > > + *   if non-NULL, returns the amount of space in the ring after the
> > > + *   enqueue operation has finished.
> > > + * @return
> > > + *   - n: Actual number of objects enqueued.
> > > + */
> > > +static __rte_always_inline unsigned int
> > > +rte_st_ring_enqueue_at_tail_burst(struct rte_st_ring *r,
> > > +				  void * const *obj_table, unsigned int n,
> > > +				  unsigned int *free_space)
> > > +{
> > > +	return rte_st_ring_enqueue_at_tail_burst_elem(r, obj_table,
> > > +			sizeof(void *), n, free_space);
> > > +}
> > > +
> > > +/**
> > > + * Enqueue one object on a ST ring at tail.
> > > + *
> > > + * This function copies one object at the tail of the ring and
> > > + * moves the tail index (backwards).
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @param obj
> > > + *   A pointer to the object to be added.
> > > + * @return
> > > + *   - 0: Success; objects enqueued.
> > > + *   - -ENOBUFS: Not enough room in the ring to enqueue; no object is
> > enqueued.
> > > + */
> > > +static __rte_always_inline int
> > > +rte_st_ring_enqueue_at_tail(struct rte_st_ring *r, void *obj) {
> > > +	return rte_st_ring_enqueue_at_tail_elem(r, &obj, sizeof(void *)); }
> > > +
> > > +/**
> > > + * Dequeue a fixed number of objects from a ST ring.
> > > + *
> > > + * This function copies the objects from the tail of the ring and
> > > + * moves the tail index.
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @param obj_table
> > > + *   A pointer to a table of void * pointers (objects) that will be filled.
> > > + * @param n
> > > + *   The number of objects to dequeue from the ring to the obj_table.
> > > + * @param available
> > > + *   If non-NULL, returns the number of remaining ring entries after the
> > > + *   dequeue has finished.
> > > + * @return
> > > + *   The number of objects dequeued, either 0 or n
> > > + */
> > > +static __rte_always_inline unsigned int
> > > +rte_st_ring_dequeue_bulk(struct rte_st_ring *r, void **obj_table, unsigned
> > int n,
> > > +		unsigned int *available)
> > > +{
> > > +	return rte_st_ring_dequeue_bulk_elem(r, obj_table, sizeof(void *),
> > > +			n, available);
> > > +}
> > > +
> > > +/**
> > > + * Dequeue upto a maximum number of objects from a ST ring.
> > > + *
> > > + * This function copies the objects from the tail of the ring and
> > > + * moves the tail index.
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @param obj_table
> > > + *   A pointer to a table of void * pointers (objects) that will be filled.
> > > + * @param n
> > > + *   The number of objects to dequeue from the ring to the obj_table.
> > > + * @param available
> > > + *   If non-NULL, returns the number of remaining ring entries after the
> > > + *   dequeue has finished.
> > > + * @return
> > > + *   - Number of objects dequeued
> > > + */
> > > +static __rte_always_inline unsigned int
> > > +rte_st_ring_dequeue_burst(struct rte_st_ring *r, void **obj_table,
> > > +		unsigned int n, unsigned int *available) {
> > > +	return rte_st_ring_dequeue_burst_elem(r, obj_table, sizeof(void *),
> > > +			n, available);
> > > +}
> > > +
> > > +/**
> > > + * Dequeue one object from a ST ring.
> > > + *
> > > + * This function copies one object from the tail of the ring and
> > > + * moves the tail index.
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @param obj_p
> > > + *   A pointer to a void * pointer (object) that will be filled.
> > > + * @return
> > > + *   - 0: Success, objects dequeued.
> > > + *   - -ENOENT: Not enough entries in the ring to dequeue, no object is
> > > + *     dequeued.
> > > + */
> > > +static __rte_always_inline int
> > > +rte_st_ring_dequeue(struct rte_st_ring *r, void **obj_p) {
> > > +	return rte_st_ring_dequeue_elem(r, obj_p, sizeof(void *)); }
> > > +
> > > +/**
> > > + * Dequeue a fixed number of objects from a ST ring from the head.
> > > + *
> > > + * This function copies the objects from the head of the ring and
> > > + * moves the head index (backwards).
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @param obj_table
> > > + *   A pointer to a table of void * pointers (objects) that will be filled.
> > > + * @param n
> > > + *   The number of objects to dequeue from the ring to the obj_table.
> > > + * @param available
> > > + *   If non-NULL, returns the number of remaining ring entries after the
> > > + *   dequeue has finished.
> > > + * @return
> > > + *   The number of objects dequeued, either 0 or n
> > > + */
> > > +static __rte_always_inline unsigned int
> > > +rte_st_ring_dequeue_at_head_bulk(struct rte_st_ring *r, void **obj_table,
> > unsigned int n,
> > > +		unsigned int *available)
> > > +{
> > > +	return rte_st_ring_dequeue_bulk_elem(r, obj_table, sizeof(void *),
> > > +			n, available);
> > > +}
> > > +
> > > +/**
> > > + * Dequeue upto a maximum number of objects from a ST ring from the
> > head.
> > > + *
> > > + * This function copies the objects from the head of the ring and
> > > + * moves the head index (backwards).
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @param obj_table
> > > + *   A pointer to a table of void * pointers (objects) that will be filled.
> > > + * @param n
> > > + *   The number of objects to dequeue from the ring to the obj_table.
> > > + * @param available
> > > + *   If non-NULL, returns the number of remaining ring entries after the
> > > + *   dequeue has finished.
> > > + * @return
> > > + *   - Number of objects dequeued
> > > + */
> > > +static __rte_always_inline unsigned int
> > > +rte_st_ring_dequeue_at_head_burst(struct rte_st_ring *r, void
> > **obj_table,
> > > +		unsigned int n, unsigned int *available) {
> > > +	return rte_st_ring_dequeue_burst_elem(r, obj_table, sizeof(void *),
> > > +			n, available);
> > > +}
> > > +
> > > +/**
> > > + * Dequeue one object from a ST ring from the head.
> > > + *
> > > + * This function copies the objects from the head of the ring and
> > > + * moves the head index (backwards).
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @param obj_p
> > > + *   A pointer to a void * pointer (object) that will be filled.
> > > + * @return
> > > + *   - 0: Success, objects dequeued.
> > > + *   - -ENOENT: Not enough entries in the ring to dequeue, no object is
> > > + *     dequeued.
> > > + */
> > > +static __rte_always_inline int
> > > +rte_st_ring_at_head_dequeue(struct rte_st_ring *r, void **obj_p) {
> > > +	return rte_st_ring_dequeue_elem(r, obj_p, sizeof(void *)); }
> > > +
> > > +/**
> > > + * Flush a ST ring.
> > > + *
> > > + * This function flush all the elements in a ST ring
> > > + *
> > > + * @warning
> > > + * Make sure the ring is not in use while calling this function.
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + */
> > > +void
> > > +rte_st_ring_reset(struct rte_st_ring *r);
> > > +
> > > +/**
> > > + * Return the number of entries in a ST ring.
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @return
> > > + *   The number of entries in the ring.
> > > + */
> > > +static inline unsigned int
> > > +rte_st_ring_count(const struct rte_st_ring *r) {
> > > +	uint32_t count = (r->head - r->tail) & r->mask;
> > > +	return count;
> > > +}
> > > +
> > > +/**
> > > + * Return the number of free entries in a ST ring.
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @return
> > > + *   The number of free entries in the ring.
> > > + */
> > > +static inline unsigned int
> > > +rte_st_ring_free_count(const struct rte_st_ring *r) {
> > > +	return r->capacity - rte_st_ring_count(r); }
> > > +
> > > +/**
> > > + * Test if a ST ring is full.
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @return
> > > + *   - 1: The ring is full.
> > > + *   - 0: The ring is not full.
> > > + */
> > > +static inline int
> > > +rte_st_ring_full(const struct rte_st_ring *r) {
> > > +	return rte_st_ring_free_count(r) == 0; }
> > > +
> > > +/**
> > > + * Test if a ST ring is empty.
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @return
> > > + *   - 1: The ring is empty.
> > > + *   - 0: The ring is not empty.
> > > + */
> > > +static inline int
> > > +rte_st_ring_empty(const struct rte_st_ring *r) {
> > > +	return r->tail == r->head;
> > > +}
> > > +
> > > +/**
> > > + * Return the size of the ring.
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @return
> > > + *   The size of the data store used by the ring.
> > > + *   NOTE: this is not the same as the usable space in the ring. To query that
> > > + *   use ``rte_st_ring_get_capacity()``.
> > > + */
> > > +static inline unsigned int
> > > +rte_st_ring_get_size(const struct rte_st_ring *r) {
> > > +	return r->size;
> > > +}
> > > +
> > > +/**
> > > + * Return the number of elements which can be stored in the ring.
> > > + *
> > > + * @param r
> > > + *   A pointer to the ring structure.
> > > + * @return
> > > + *   The usable size of the ring.
> > > + */
> > > +static inline unsigned int
> > > +rte_st_ring_get_capacity(const struct rte_st_ring *r) {
> > > +	return r->capacity;
> > > +}
> > > +
> > > +/**
> > > + * Dump the status of all rings on the console
> > > + *
> > > + * @param f
> > > + *   A pointer to a file for output
> > > + */
> > > +void rte_st_ring_list_dump(FILE *f);
> > > +
> > > +/**
> > > + * Search a ST ring from its name
> > > + *
> > > + * @param name
> > > + *   The name of the ring.
> > > + * @return
> > > + *   The pointer to the ring matching the name, or NULL if not found,
> > > + *   with rte_errno set appropriately. Possible rte_errno values include:
> > > + *    - ENOENT - required entry not available to return.
> > > + */
> > > +struct rte_st_ring *rte_st_ring_lookup(const char *name);
> > > +
> > > +#ifdef __cplusplus
> > > +}
> > > +#endif
> > > +
> > > +#endif /* _RTE_ST_RING_H_ */
> > > --
> > > 2.25.1
> > >
  
Stephen Hemminger April 1, 2024, 2:05 p.m. UTC | #14
On Sun, 31 Mar 2024 20:37:27 -0500
Aditya Ambadipudi <aditya.ambadipudi@arm.com> wrote:

> As previously discussed in the mailing list [1] we are sending out this
> patch that provides the implementation and unit test cases for the
> RTE_DEQUE library. This includes functions for creating a RTE_DEQUE 
> object. Allocating memory to it. Deleting that object and free'ing the
> memory associated with it. Enqueue/Dequeue functions. Functions for 
> zero-copy API.
> 
> [1] https://mails.dpdk.org/archives/dev/2023-August/275003.html

Does this build without errors with the Microsoft Visual C compiler?

Want to make sure that all new code does not create more work for the
Windows maintainers.
  
Aditya Ambadipudi April 1, 2024, 10:28 p.m. UTC | #15
Thanks, Stephen, for the comment.

Unfortunately, we don't have the dev setup nor the resources to test out this change using MSVC.

Thank you,
Aditya Ambadipudi
  
Tyler Retzlaff April 2, 2024, 12:05 a.m. UTC | #16
On Mon, Apr 01, 2024 at 10:28:52PM +0000, Aditya Ambadipudi wrote:
> Thanks, Stephen, for the comment.
> 
> Unfortunately, we don't have the dev setup nor the resources to test out this change using MSVC.

what are the dependencies of this lib?

you've provided an agnostic api and unit tests, you can enable it in the
build and the CI will provide a minimum test bar.

> 
> Thank you,
> Aditya Ambadipudi
> 
> 
> ________________________________
> From: Stephen Hemminger <stephen@networkplumber.org>
> Sent: Monday, April 1, 2024 9:05 AM
> To: Aditya Ambadipudi <Aditya.Ambadipudi@arm.com>
> Cc: dev@dpdk.org <dev@dpdk.org>; jackmin@nvidia.com <jackmin@nvidia.com>; matan@nvidia.com <matan@nvidia.com>; viacheslavo@nvidia.com <viacheslavo@nvidia.com>; roretzla@linux.microsoft.com <roretzla@linux.microsoft.com>; konstantin.v.ananyev@yandex.ru <konstantin.v.ananyev@yandex.ru>; konstantin.ananyev@huawei.com <konstantin.ananyev@huawei.com>; mb@smartsharesystems.com <mb@smartsharesystems.com>; hofors@lysator.liu.se <hofors@lysator.liu.se>; Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com>; Dhruv Tripathi <Dhruv.Tripathi@arm.com>; Wathsala Wathawana Vithanage <wathsala.vithanage@arm.com>; ganeshaditya1@gmail.com <ganeshaditya1@gmail.com>; nd <nd@arm.com>
> Subject: Re: [PATCH v1 0/2] deque: add multithread unsafe deque library
> 
> On Sun, 31 Mar 2024 20:37:27 -0500
> Aditya Ambadipudi <aditya.ambadipudi@arm.com> wrote:
> 
> > As previously discussed in the mailing list [1] we are sending out this
> > patch that provides the implementation and unit test cases for the
> > RTE_DEQUE library. This includes functions for creating a RTE_DEQUE
> > object. Allocating memory to it. Deleting that object and free'ing the
> > memory associated with it. Enqueue/Dequeue functions. Functions for
> > zero-copy API.
> >
> > [1] https://mails.dpdk.org/archives/dev/2023-August/275003.html
> 
> Does this build without errors with the Microsoft Visual C compiler?
> 
> Want to make sure that all new code does not create more work for the
> Windows maintainers.
  
Stephen Hemminger April 2, 2024, 12:47 a.m. UTC | #17
On Mon, 1 Apr 2024 22:28:52 +0000
Aditya Ambadipudi <Aditya.Ambadipudi@arm.com> wrote:

> Thanks, Stephen, for the comment.
> 
> Unfortunately, we don't have the dev setup nor the resources to test out this change using MSVC.
> 
> Thank you,
> Aditya Ambadipudi

All it requires is the community version of MSVC which is free. And setting up a Windows
VM with KVM is free and easy.

IMHO all new libraries have to build on all environments, unless they are enabling
platform specific features.
  
Honnappa Nagarahalli April 2, 2024, 1:35 a.m. UTC | #18
> On Apr 1, 2024, at 7:47 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
> 
> On Mon, 1 Apr 2024 22:28:52 +0000
> Aditya Ambadipudi <Aditya.Ambadipudi@arm.com> wrote:
> 
>> Thanks, Stephen, for the comment.
>> 
>> Unfortunately, we don't have the dev setup nor the resources to test out this change using MSVC.
>> 
>> Thank you,
>> Aditya Ambadipudi
> 
> All it requires is the community version of MSVC which is free. And setting up a Windows
> VM with KVM is free and easy.
> 
> IMHO all new libraries have to build on all environments, unless they are enabling
> platform specific features.
I see that UNH CI is running Windows VMs, the tests are passing there. So, we do not need to anything.
  
Stephen Hemminger April 2, 2024, 2 a.m. UTC | #19
On Tue, 2 Apr 2024 01:35:28 +0000
Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com> wrote:

> > On Apr 1, 2024, at 7:47 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
> > 
> > On Mon, 1 Apr 2024 22:28:52 +0000
> > Aditya Ambadipudi <Aditya.Ambadipudi@arm.com> wrote:
> >   
> >> Thanks, Stephen, for the comment.
> >> 
> >> Unfortunately, we don't have the dev setup nor the resources to test out this change using MSVC.
> >> 
> >> Thank you,
> >> Aditya Ambadipudi  
> > 
> > All it requires is the community version of MSVC which is free. And setting up a Windows
> > VM with KVM is free and easy.
> > 
> > IMHO all new libraries have to build on all environments, unless they are enabling
> > platform specific features.  
> I see that UNH CI is running Windows VMs, the tests are passing there. So, we do not need to anything.
> 

That only tests the clang part.
You need to modify lib/meson.build to get it tested with the windows compiler.
  
Honnappa Nagarahalli April 2, 2024, 2:14 a.m. UTC | #20
> On Apr 1, 2024, at 9:00 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
> 
> On Tue, 2 Apr 2024 01:35:28 +0000
> Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com> wrote:
> 
>>> On Apr 1, 2024, at 7:47 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
>>> 
>>> On Mon, 1 Apr 2024 22:28:52 +0000
>>> Aditya Ambadipudi <Aditya.Ambadipudi@arm.com> wrote:
>>> 
>>>> Thanks, Stephen, for the comment.
>>>> 
>>>> Unfortunately, we don't have the dev setup nor the resources to test out this change using MSVC.
>>>> 
>>>> Thank you,
>>>> Aditya Ambadipudi  
>>> 
>>> All it requires is the community version of MSVC which is free. And setting up a Windows
>>> VM with KVM is free and easy.
>>> 
>>> IMHO all new libraries have to build on all environments, unless they are enabling
>>> platform specific features.  
>> I see that UNH CI is running Windows VMs, the tests are passing there. So, we do not need to anything.
>> 
> 
> That only tests the clang part.
> You need to modify lib/meson.build to get it tested with the windows compiler.
Any idea on when is this getting added to CI?
  
Stephen Hemminger April 2, 2024, 2:53 a.m. UTC | #21
On Tue, 2 Apr 2024 02:14:06 +0000
Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com> wrote:

> > On Apr 1, 2024, at 9:00 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
> > 
> > On Tue, 2 Apr 2024 01:35:28 +0000
> > Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com> wrote:
> >   
> >>> On Apr 1, 2024, at 7:47 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
> >>> 
> >>> On Mon, 1 Apr 2024 22:28:52 +0000
> >>> Aditya Ambadipudi <Aditya.Ambadipudi@arm.com> wrote:
> >>>   
> >>>> Thanks, Stephen, for the comment.
> >>>> 
> >>>> Unfortunately, we don't have the dev setup nor the resources to test out this change using MSVC.
> >>>> 
> >>>> Thank you,
> >>>> Aditya Ambadipudi    
> >>> 
> >>> All it requires is the community version of MSVC which is free. And setting up a Windows
> >>> VM with KVM is free and easy.
> >>> 
> >>> IMHO all new libraries have to build on all environments, unless they are enabling
> >>> platform specific features.    
> >> I see that UNH CI is running Windows VMs, the tests are passing there. So, we do not need to anything.
> >>   
> > 
> > That only tests the clang part.
> > You need to modify lib/meson.build to get it tested with the windows compiler.  
> Any idea on when is this getting added to CI?

You need to add this to next version of the patch.
I tried it and MSVC has no problems with the new code.

Another issue is the naming. Right now the choice of 'deque' generates lots of
checkpatch errors, and every bit of new code that uses the library will get a warning
as well. Can you think of a better name?

diff --git a/lib/meson.build b/lib/meson.build
index 8c8c1e98e2..127e4dc68c 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -75,6 +75,7 @@ if is_ms_compiler
             'kvargs',
             'telemetry',
             'eal',
+            'deque',
             'ring',
     ]
 endif
  
Tyler Retzlaff April 2, 2024, 4:20 a.m. UTC | #22
On Tue, Apr 02, 2024 at 03:03:13AM +0000, Aditya Ambadipudi wrote:
> Hello Stephen,
> 
> I have a copy of CLRS with me. And Deque is a very standard word in computer science. Even CLRS which is considered one of the most foundational books in computer science uses the word deque.

i'm kind of inclined to agree with this. double ended queue is pretty
well known as ``deque`` perhaps most notably from stl.

https://en.cppreference.com/w/cpp/container/deque

i would however strongly advise that there be no use of ``deque`` bare
(without the rte_ prefix) in any public header. (i.e. inline function
variables , parameter names, etc...). that would almost certainly result
in frustration for C++ consumers of dpdk that may be doing the
following:

#include <deque>
#include <rte_deque.h>

using namespace std;

a quick pass of the patches and i don't see any instances without the
rte_ prefix so only cautioning that we would want to avoid it.

> 
> I don't think there is any better word to describe the datastructure we are building other than "deque".
> 
> Is there a way to add an exception for that word in the dictionary words check we run? I genuinely think the readability of this library that we are building will suffer if we don't use the word "deque" here.
> 
> Thank you,
> Aditya Ambadipudi
> 
> [image/jpeg][image/jpeg]
> 
> ________________________________
> From: Stephen Hemminger <stephen@networkplumber.org>
> Sent: Monday, April 1, 2024 9:53 PM
> To: Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com>
> Cc: Aditya Ambadipudi <Aditya.Ambadipudi@arm.com>; dev@dpdk.org <dev@dpdk.org>; jackmin@nvidia.com <jackmin@nvidia.com>; matan@nvidia.com <matan@nvidia.com>; viacheslavo@nvidia.com <viacheslavo@nvidia.com>; roretzla@linux.microsoft.com <roretzla@linux.microsoft.com>; konstantin.v.ananyev@yandex.ru <konstantin.v.ananyev@yandex.ru>; konstantin.ananyev@huawei.com <konstantin.ananyev@huawei.com>; mb@smartsharesystems.com <mb@smartsharesystems.com>; hofors@lysator.liu.se <hofors@lysator.liu.se>; Dhruv Tripathi <Dhruv.Tripathi@arm.com>; Wathsala Wathawana Vithanage <wathsala.vithanage@arm.com>; nd <nd@arm.com>
> Subject: Re: [PATCH v1 0/2] deque: add multithread unsafe deque library
> 
> On Tue, 2 Apr 2024 02:14:06 +0000
> Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com> wrote:
> 
> > > On Apr 1, 2024, at 9:00 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
> > >
> > > On Tue, 2 Apr 2024 01:35:28 +0000
> > > Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com> wrote:
> > >
> > >>> On Apr 1, 2024, at 7:47 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
> > >>>
> > >>> On Mon, 1 Apr 2024 22:28:52 +0000
> > >>> Aditya Ambadipudi <Aditya.Ambadipudi@arm.com> wrote:
> > >>>
> > >>>> Thanks, Stephen, for the comment.
> > >>>>
> > >>>> Unfortunately, we don't have the dev setup nor the resources to test out this change using MSVC.
> > >>>>
> > >>>> Thank you,
> > >>>> Aditya Ambadipudi
> > >>>
> > >>> All it requires is the community version of MSVC which is free. And setting up a Windows
> > >>> VM with KVM is free and easy.
> > >>>
> > >>> IMHO all new libraries have to build on all environments, unless they are enabling
> > >>> platform specific features.
> > >> I see that UNH CI is running Windows VMs, the tests are passing there. So, we do not need to anything.
> > >>
> > >
> > > That only tests the clang part.
> > > You need to modify lib/meson.build to get it tested with the windows compiler.
> > Any idea on when is this getting added to CI?
> 
> You need to add this to next version of the patch.
> I tried it and MSVC has no problems with the new code.
> 
> Another issue is the naming. Right now the choice of 'deque' generates lots of
> checkpatch errors, and every bit of new code that uses the library will get a warning
> as well. Can you think of a better name?
> 
> diff --git a/lib/meson.build b/lib/meson.build
> index 8c8c1e98e2..127e4dc68c 100644
> --- a/lib/meson.build
> +++ b/lib/meson.build
> @@ -75,6 +75,7 @@ if is_ms_compiler
>              'kvargs',
>              'telemetry',
>              'eal',
> +            'deque',
>              'ring',
>      ]
>  endif
  
Tyler Retzlaff April 2, 2024, 4:20 a.m. UTC | #23
On Mon, Apr 01, 2024 at 07:53:48PM -0700, Stephen Hemminger wrote:
> On Tue, 2 Apr 2024 02:14:06 +0000
> Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com> wrote:
> 
> > > On Apr 1, 2024, at 9:00 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
> > > 
> > > On Tue, 2 Apr 2024 01:35:28 +0000
> > > Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com> wrote:
> > >   
> > >>> On Apr 1, 2024, at 7:47 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
> > >>> 
> > >>> On Mon, 1 Apr 2024 22:28:52 +0000
> > >>> Aditya Ambadipudi <Aditya.Ambadipudi@arm.com> wrote:
> > >>>   
> > >>>> Thanks, Stephen, for the comment.
> > >>>> 
> > >>>> Unfortunately, we don't have the dev setup nor the resources to test out this change using MSVC.
> > >>>> 
> > >>>> Thank you,
> > >>>> Aditya Ambadipudi    
> > >>> 
> > >>> All it requires is the community version of MSVC which is free. And setting up a Windows
> > >>> VM with KVM is free and easy.
> > >>> 
> > >>> IMHO all new libraries have to build on all environments, unless they are enabling
> > >>> platform specific features.    
> > >> I see that UNH CI is running Windows VMs, the tests are passing there. So, we do not need to anything.
> > >>   
> > > 
> > > That only tests the clang part.
> > > You need to modify lib/meson.build to get it tested with the windows compiler.  
> > Any idea on when is this getting added to CI?
> 
> You need to add this to next version of the patch.
> I tried it and MSVC has no problems with the new code.
> 
> Another issue is the naming. Right now the choice of 'deque' generates lots of
> checkpatch errors, and every bit of new code that uses the library will get a warning
> as well. Can you think of a better name?
> 
> diff --git a/lib/meson.build b/lib/meson.build
> index 8c8c1e98e2..127e4dc68c 100644
> --- a/lib/meson.build
> +++ b/lib/meson.build
> @@ -75,6 +75,7 @@ if is_ms_compiler
>              'kvargs',
>              'telemetry',
>              'eal',
> +            'deque',
>              'ring',
>      ]
>  endif

please do this.

thanks
  
Mattias Rönnblom April 2, 2024, 6:05 a.m. UTC | #24
On 2024-04-02 02:47, Stephen Hemminger wrote:
> On Mon, 1 Apr 2024 22:28:52 +0000
> Aditya Ambadipudi <Aditya.Ambadipudi@arm.com> wrote:
> 
>> Thanks, Stephen, for the comment.
>>
>> Unfortunately, we don't have the dev setup nor the resources to test out this change using MSVC.
>>
>> Thank you,
>> Aditya Ambadipudi
> 
> All it requires is the community version of MSVC which is free. And setting up a Windows
> VM with KVM is free and easy.
> 
> IMHO all new libraries have to build on all environments, unless they are enabling
> platform specific features.

Requiring all contributors to build and test on what is, in this 
context, a pretty obscure platform with a pretty obscure compiler seems 
like a bad idea to me.

It will raise the bar for contributions further.

In principle I agree though. Your contribution should not only build, 
but also run (and be tested) on all platforms. Otherwise, Windows isn't 
supported in the upstream, but rather we have a Windows port (which 
happens to live in the same source tree).

I never tested any contribution on a FreeBSD system, but at least those 
use the de-facto standard compilers and a standard API (POSIX), so the 
likelihood of things actually working is greater (but maybe not great 
enough).

Surely, this is something the tech board must have discussed when it 
agreed to supporting Windows *and* MSVC. Many if not most of the 
man-hours involved won't be spent by the Windows maintainer, but the 
individual future contributors.
  
Stephen Hemminger April 2, 2024, 3:25 p.m. UTC | #25
On Tue, 2 Apr 2024 08:05:41 +0200
Mattias Rönnblom <hofors@lysator.liu.se> wrote:

> On 2024-04-02 02:47, Stephen Hemminger wrote:
> > On Mon, 1 Apr 2024 22:28:52 +0000
> > Aditya Ambadipudi <Aditya.Ambadipudi@arm.com> wrote:
> >   
> >> Thanks, Stephen, for the comment.
> >>
> >> Unfortunately, we don't have the dev setup nor the resources to test out this change using MSVC.
> >>
> >> Thank you,
> >> Aditya Ambadipudi  
> > 
> > All it requires is the community version of MSVC which is free. And setting up a Windows
> > VM with KVM is free and easy.
> > 
> > IMHO all new libraries have to build on all environments, unless they are enabling
> > platform specific features.  
> 
> Requiring all contributors to build and test on what is, in this 
> context, a pretty obscure platform with a pretty obscure compiler seems 
> like a bad idea to me.
> 
> It will raise the bar for contributions further.
> 
> In principle I agree though. Your contribution should not only build, 
> but also run (and be tested) on all platforms. Otherwise, Windows isn't 
> supported in the upstream, but rather we have a Windows port (which 
> happens to live in the same source tree).
> 
> I never tested any contribution on a FreeBSD system, but at least those 
> use the de-facto standard compilers and a standard API (POSIX), so the 
> likelihood of things actually working is greater (but maybe not great 
> enough).
> 
> Surely, this is something the tech board must have discussed when it 
> agreed to supporting Windows *and* MSVC. Many if not most of the 
> man-hours involved won't be spent by the Windows maintainer, but the 
> individual future contributors.

This what CI systems are for. FreeBSD and Windows are enabled in the
build system; just need to make sure all new libraries are enabled.

That said, I am all for keeping focus. So open to discussions on
dropping lesser platforms or build environments if they interfere.
  
Stephen Hemminger April 2, 2024, 11:44 p.m. UTC | #26
On Mon, 1 Apr 2024 21:20:13 -0700
Tyler Retzlaff <roretzla@linux.microsoft.com> wrote:

> On Tue, Apr 02, 2024 at 03:03:13AM +0000, Aditya Ambadipudi wrote:
> > Hello Stephen,
> > 
> > I have a copy of CLRS with me. And Deque is a very standard word in computer science. Even CLRS which is considered one of the most foundational books in computer science uses the word deque.  
> 
> i'm kind of inclined to agree with this. double ended queue is pretty
> well known as ``deque`` perhaps most notably from stl.
> 
> https://en.cppreference.com/w/cpp/container/deque

The root cause of my complaint is the codespell dictionary used in the patchwork tests.
It is a bit of twisted path to fix this though. CI runs checkpatch.sh which runs kernel checkpatch.pl
which calls codespell library. Although codespell has an ignore list, there is no option
to feed that through in kernel's checkpatch.pl file.
  
Honnappa Nagarahalli April 3, 2024, 12:12 a.m. UTC | #27
> On Apr 2, 2024, at 6:44 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
> 
> On Mon, 1 Apr 2024 21:20:13 -0700
> Tyler Retzlaff <roretzla@linux.microsoft.com> wrote:
> 
>> On Tue, Apr 02, 2024 at 03:03:13AM +0000, Aditya Ambadipudi wrote:
>>> Hello Stephen,
>>> 
>>> I have a copy of CLRS with me. And Deque is a very standard word in computer science. Even CLRS which is considered one of the most foundational books in computer science uses the word deque.  
>> 
>> i'm kind of inclined to agree with this. double ended queue is pretty
>> well known as ``deque`` perhaps most notably from stl.
>> 
>> https://en.cppreference.com/w/cpp/container/deque
> 
> The root cause of my complaint is the codespell dictionary used in the patchwork tests.
> It is a bit of twisted path to fix this though. CI runs checkpatch.sh which runs kernel checkpatch.pl
> which calls codespell library. Although codespell has an ignore list, there is no option
> to feed that through in kernel's checkpatch.pl file.
So, not under DPDK control. May be we need to ignore the errors manually.

>
  
Honnappa Nagarahalli April 3, 2024, 4:50 p.m. UTC | #28
> On Apr 1, 2024, at 9:53 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
> 
> On Tue, 2 Apr 2024 02:14:06 +0000
> Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com> wrote:
> 
>>> On Apr 1, 2024, at 9:00 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
>>> 
>>> On Tue, 2 Apr 2024 01:35:28 +0000
>>> Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com> wrote:
>>> 
>>>>> On Apr 1, 2024, at 7:47 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
>>>>> 
>>>>> On Mon, 1 Apr 2024 22:28:52 +0000
>>>>> Aditya Ambadipudi <Aditya.Ambadipudi@arm.com> wrote:
>>>>> 
>>>>>> Thanks, Stephen, for the comment.
>>>>>> 
>>>>>> Unfortunately, we don't have the dev setup nor the resources to test out this change using MSVC.
>>>>>> 
>>>>>> Thank you,
>>>>>> Aditya Ambadipudi    
>>>>> 
>>>>> All it requires is the community version of MSVC which is free. And setting up a Windows
>>>>> VM with KVM is free and easy.
>>>>> 
>>>>> IMHO all new libraries have to build on all environments, unless they are enabling
>>>>> platform specific features.    
>>>> I see that UNH CI is running Windows VMs, the tests are passing there. So, we do not need to anything.
>>>> 
>>> 
>>> That only tests the clang part.
>>> You need to modify lib/meson.build to get it tested with the windows compiler.  
>> Any idea on when is this getting added to CI?
> 
> You need to add this to next version of the patch.
> I tried it and MSVC has no problems with the new code.
> 
> Another issue is the naming. Right now the choice of 'deque' generates lots of
> checkpatch errors, and every bit of new code that uses the library will get a warning
> as well. Can you think of a better name?
> 
> diff --git a/lib/meson.build b/lib/meson.build
> index 8c8c1e98e2..127e4dc68c 100644
> --- a/lib/meson.build
> +++ b/lib/meson.build
> @@ -75,6 +75,7 @@ if is_ms_compiler
>             'kvargs',
>             'telemetry',
>             'eal',
> +            'deque',
>             'ring',
>     ]
> endif
As discussed in Techboard meeting, the above change is not required as Tyler has a patch [1] that addresses this.

[1] https://patches.dpdk.org/project/dpdk/patch/1712076948-25853-2-git-send-email-roretzla@linux.microsoft.com/
  
Tyler Retzlaff April 3, 2024, 5:46 p.m. UTC | #29
On Wed, Apr 03, 2024 at 04:50:02PM +0000, Honnappa Nagarahalli wrote:
> 
> 
> > On Apr 1, 2024, at 9:53 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
> > 
> > On Tue, 2 Apr 2024 02:14:06 +0000
> > Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com> wrote:
> > 
> >>> On Apr 1, 2024, at 9:00 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
> >>> 
> >>> On Tue, 2 Apr 2024 01:35:28 +0000
> >>> Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com> wrote:
> >>> 
> >>>>> On Apr 1, 2024, at 7:47 PM, Stephen Hemminger <stephen@networkplumber.org> wrote:
> >>>>> 
> >>>>> On Mon, 1 Apr 2024 22:28:52 +0000
> >>>>> Aditya Ambadipudi <Aditya.Ambadipudi@arm.com> wrote:
> >>>>> 
> >>>>>> Thanks, Stephen, for the comment.
> >>>>>> 
> >>>>>> Unfortunately, we don't have the dev setup nor the resources to test out this change using MSVC.
> >>>>>> 
> >>>>>> Thank you,
> >>>>>> Aditya Ambadipudi    
> >>>>> 
> >>>>> All it requires is the community version of MSVC which is free. And setting up a Windows
> >>>>> VM with KVM is free and easy.
> >>>>> 
> >>>>> IMHO all new libraries have to build on all environments, unless they are enabling
> >>>>> platform specific features.    
> >>>> I see that UNH CI is running Windows VMs, the tests are passing there. So, we do not need to anything.
> >>>> 
> >>> 
> >>> That only tests the clang part.
> >>> You need to modify lib/meson.build to get it tested with the windows compiler.  
> >> Any idea on when is this getting added to CI?
> > 
> > You need to add this to next version of the patch.
> > I tried it and MSVC has no problems with the new code.
> > 
> > Another issue is the naming. Right now the choice of 'deque' generates lots of
> > checkpatch errors, and every bit of new code that uses the library will get a warning
> > as well. Can you think of a better name?
> > 
> > diff --git a/lib/meson.build b/lib/meson.build
> > index 8c8c1e98e2..127e4dc68c 100644
> > --- a/lib/meson.build
> > +++ b/lib/meson.build
> > @@ -75,6 +75,7 @@ if is_ms_compiler
> >             'kvargs',
> >             'telemetry',
> >             'eal',
> > +            'deque',
> >             'ring',
> >     ]
> > endif
> As discussed in Techboard meeting, the above change is not required as Tyler has a patch [1] that addresses this.
> 
> [1] https://patches.dpdk.org/project/dpdk/patch/1712076948-25853-2-git-send-email-roretzla@linux.microsoft.com/

agreed, please merge my series first to get the correct outcome.

thanks!
  
Stephen Hemminger April 3, 2024, 11:52 p.m. UTC | #30
On Wed, 3 Apr 2024 00:12:19 +0000
Honnappa Nagarahalli <Honnappa.Nagarahalli@arm.com> wrote:

> > https://en.cppreference.com/w/cpp/container/deque  
> > 
> > The root cause of my complaint is the codespell dictionary used in the patchwork tests.
> > It is a bit of twisted path to fix this though. CI runs checkpatch.sh which runs kernel checkpatch.pl
> > which calls codespell library. Although codespell has an ignore list, there is no option
> > to feed that through in kernel's checkpatch.pl file.  

> So, not under DPDK control. May be we need to ignore the errors manually.

One solution would be for DPDK to provide its own codespell dictionary so that
all users and CI would see the same word list. By cloning the upstream dictionary
and trimming away know issues; maybe even have a devtools script to get the current
dictionary from github and fix it.

Then we could get rid of false positives for 'deque' and 'stdio'
  

Patch

diff --git a/lib/st_ring/rte_st_ring.h b/lib/st_ring/rte_st_ring.h
new file mode 100644
index 0000000000..8cb8832591
--- /dev/null
+++ b/lib/st_ring/rte_st_ring.h
@@ -0,0 +1,567 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Arm Limited
+ */
+
+#ifndef _RTE_ST_RING_H_
+#define _RTE_ST_RING_H_
+
+/**
+ * @file
+ * RTE Signle Thread Ring (ST Ring)
+ *
+ * The ST Ring is a fixed-size queue intended to be accessed
+ * by one thread at a time. It does not provide concurrent access to
+ * multiple threads. If there are multiple threads accessing the ST ring,
+ * then the threads have to use locks to protect the ring from
+ * getting corrupted.
+ *
+ * - FIFO (First In First Out)
+ * - Maximum size is fixed; the pointers are stored in a table.
+ * - Consumer and producer part of same thread.
+ * - Multi-thread producers and consumers need locking.
+ * - Single/Bulk/burst dequeue at Tail or Head
+ * - Single/Bulk/burst enqueue at Head or Tail
+ *
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <rte_st_ring_core.h>
+#include <rte_st_ring_elem.h>
+
+/**
+ * Calculate the memory size needed for a ST ring
+ *
+ * This function returns the number of bytes needed for a ST ring, given
+ * the number of elements in it. This value is the sum of the size of
+ * the structure rte_st_ring and the size of the memory needed by the
+ * elements. The value is aligned to a cache line size.
+ *
+ * @param count
+ *   The number of elements in the ring (must be a power of 2).
+ * @return
+ *   - The memory size needed for the ST ring on success.
+ *   - -EINVAL if count is not a power of 2.
+ */
+ssize_t rte_st_ring_get_memsize(unsigned int count);
+
+/**
+ * Initialize a ST ring structure.
+ *
+ * Initialize a ST ring structure in memory pointed by "r". The size of the
+ * memory area must be large enough to store the ring structure and the
+ * object table. It is advised to use rte_st_ring_get_memsize() to get the
+ * appropriate size.
+ *
+ * The ST ring size is set to *count*, which must be a power of two.
+ * The real usable ring size is *count-1* instead of *count* to
+ * differentiate a full ring from an empty ring.
+ *
+ * The ring is not added in RTE_TAILQ_ST_RING global list. Indeed, the
+ * memory given by the caller may not be shareable among dpdk
+ * processes.
+ *
+ * @param r
+ *   The pointer to the ring structure followed by the elements table.
+ * @param name
+ *   The name of the ring.
+ * @param count
+ *   The number of elements in the ring (must be a power of 2,
+ *   unless RTE_ST_RING_F_EXACT_SZ is set in flags).
+ * @param flags
+ *   An OR of the following:
+ *   - RTE_ST_RING_F_EXACT_SZ: If this flag is set, the ring will hold
+ *     exactly the requested number of entries, and the requested size
+ *     will be rounded up to the next power of two, but the usable space
+ *     will be exactly that requested. Worst case, if a power-of-2 size is
+ *     requested, half the ring space will be wasted.
+ *     Without this flag set, the ring size requested must be a power of 2,
+ *     and the usable space will be that size - 1.
+ * @return
+ *   0 on success, or a negative value on error.
+ */
+int rte_st_ring_init(struct rte_st_ring *r, const char *name,
+	unsigned int count, unsigned int flags);
+
+/**
+ * Create a new ST ring named *name* in memory.
+ *
+ * This function uses ``memzone_reserve()`` to allocate memory. Then it
+ * calls rte_st_ring_init() to initialize an empty ring.
+ *
+ * The new ring size is set to *count*, which must be a power of two.
+ * The real usable ring size is *count-1* instead of *count* to
+ * differentiate a full ring from an empty ring.
+ *
+ * The ring is added in RTE_TAILQ_ST_RING list.
+ *
+ * @param name
+ *   The name of the ring.
+ * @param count
+ *   The size of the ring (must be a power of 2,
+ *   unless RTE_ST_RING_F_EXACT_SZ is set in flags).
+ * @param socket_id
+ *   The *socket_id* argument is the socket identifier in case of
+ *   NUMA. The value can be *SOCKET_ID_ANY* if there is no NUMA
+ *   constraint for the reserved zone.
+ * @param flags
+ *   - RTE_ST_RING_F_EXACT_SZ: If this flag is set, the ring will hold exactly the
+ *     requested number of entries, and the requested size will be rounded up
+ *     to the next power of two, but the usable space will be exactly that
+ *     requested. Worst case, if a power-of-2 size is requested, half the
+ *     ring space will be wasted.
+ *     Without this flag set, the ring size requested must be a power of 2,
+ *     and the usable space will be that size - 1.
+ * @return
+ *   On success, the pointer to the new allocated ring. NULL on error with
+ *    rte_errno set appropriately. Possible errno values include:
+ *    - E_RTE_NO_CONFIG - function could not get pointer to rte_config structure
+ *    - EINVAL - count provided is not a power of 2
+ *    - ENOSPC - the maximum number of memzones has already been allocated
+ *    - EEXIST - a memzone with the same name already exists
+ *    - ENOMEM - no appropriate memory area found in which to create memzone
+ */
+struct rte_st_ring *rte_st_ring_create(const char *name, unsigned int count,
+				 int socket_id, unsigned int flags);
+
+/**
+ * De-allocate all memory used by the ring.
+ *
+ * @param r
+ *   Ring to free.
+ *   If NULL then, the function does nothing.
+ */
+void rte_st_ring_free(struct rte_st_ring *r);
+
+/**
+ * Dump the status of the ring to a file.
+ *
+ * @param f
+ *   A pointer to a file for output
+ * @param r
+ *   A pointer to the ring structure.
+ */
+void rte_st_ring_dump(FILE *f, const struct rte_st_ring *r);
+
+/**
+ * Enqueue fixed number of objects on a ST ring.
+ *
+ * This function copies the objects at the head of the ring and
+ * moves the head index.
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @param obj_table
+ *   A pointer to a table of void * pointers (objects).
+ * @param n
+ *   The number of objects to add in the ring from the obj_table.
+ * @param free_space
+ *   if non-NULL, returns the amount of space in the ring after the
+ *   enqueue operation has finished.
+ * @return
+ *   The number of objects enqueued, either 0 or n
+ */
+static __rte_always_inline unsigned int
+rte_st_ring_enqueue_bulk(struct rte_st_ring *r, void * const *obj_table,
+		      unsigned int n, unsigned int *free_space)
+{
+	return rte_st_ring_enqueue_bulk_elem(r, obj_table, sizeof(void *),
+			n, free_space);
+}
+
+/**
+ * Enqueue upto a maximum number of objects on a ST ring.
+ *
+ * This function copies the objects at the head of the ring and
+ * moves the head index.
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @param obj_table
+ *   A pointer to a table of void * pointers (objects).
+ * @param n
+ *   The number of objects to add in the ring from the obj_table.
+ * @param free_space
+ *   if non-NULL, returns the amount of space in the ring after the
+ *   enqueue operation has finished.
+ * @return
+ *   - n: Actual number of objects enqueued.
+ */
+static __rte_always_inline unsigned int
+rte_st_ring_enqueue_burst(struct rte_st_ring *r, void * const *obj_table,
+		      unsigned int n, unsigned int *free_space)
+{
+	return rte_st_ring_enqueue_burst_elem(r, obj_table, sizeof(void *),
+			n, free_space);
+}
+
+/**
+ * Enqueue one object on a ST ring.
+ *
+ * This function copies one object at the head of the ring and
+ * moves the head index.
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @param obj
+ *   A pointer to the object to be added.
+ * @return
+ *   - 0: Success; objects enqueued.
+ *   - -ENOBUFS: Not enough room in the ring to enqueue; no object is enqueued.
+ */
+static __rte_always_inline int
+rte_st_ring_enqueue(struct rte_st_ring *r, void *obj)
+{
+	return rte_st_ring_enqueue_elem(r, &obj, sizeof(void *));
+}
+
+/**
+ * Enqueue fixed number of objects on a ST ring at the tail.
+ *
+ * This function copies the objects at the tail of the ring and
+ * moves the tail index (backwards).
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @param obj_table
+ *   A pointer to a table of void * pointers (objects).
+ * @param n
+ *   The number of objects to add in the ring from the obj_table.
+ * @param free_space
+ *   if non-NULL, returns the amount of space in the ring after the
+ *   enqueue operation has finished.
+ * @return
+ *   The number of objects enqueued, either 0 or n
+ */
+static __rte_always_inline unsigned int
+rte_st_ring_enqueue_at_tail_bulk(struct rte_st_ring *r,
+				 void * const *obj_table, unsigned int n,
+				 unsigned int *free_space)
+{
+	return rte_st_ring_enqueue_at_tail_bulk_elem(r, obj_table,
+			sizeof(void *), n, free_space);
+}
+
+/**
+ * Enqueue upto a maximum number of objects on a ST ring at the tail.
+ *
+ * This function copies the objects at the tail of the ring and
+ * moves the tail index (backwards).
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @param obj_table
+ *   A pointer to a table of void * pointers (objects).
+ * @param n
+ *   The number of objects to add in the ring from the obj_table.
+ * @param free_space
+ *   if non-NULL, returns the amount of space in the ring after the
+ *   enqueue operation has finished.
+ * @return
+ *   - n: Actual number of objects enqueued.
+ */
+static __rte_always_inline unsigned int
+rte_st_ring_enqueue_at_tail_burst(struct rte_st_ring *r,
+				  void * const *obj_table, unsigned int n,
+				  unsigned int *free_space)
+{
+	return rte_st_ring_enqueue_at_tail_burst_elem(r, obj_table,
+			sizeof(void *), n, free_space);
+}
+
+/**
+ * Enqueue one object on a ST ring at tail.
+ *
+ * This function copies one object at the tail of the ring and
+ * moves the tail index (backwards).
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @param obj
+ *   A pointer to the object to be added.
+ * @return
+ *   - 0: Success; objects enqueued.
+ *   - -ENOBUFS: Not enough room in the ring to enqueue; no object is enqueued.
+ */
+static __rte_always_inline int
+rte_st_ring_enqueue_at_tail(struct rte_st_ring *r, void *obj)
+{
+	return rte_st_ring_enqueue_at_tail_elem(r, &obj, sizeof(void *));
+}
+
+/**
+ * Dequeue a fixed number of objects from a ST ring.
+ *
+ * This function copies the objects from the tail of the ring and
+ * moves the tail index.
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @param obj_table
+ *   A pointer to a table of void * pointers (objects) that will be filled.
+ * @param n
+ *   The number of objects to dequeue from the ring to the obj_table.
+ * @param available
+ *   If non-NULL, returns the number of remaining ring entries after the
+ *   dequeue has finished.
+ * @return
+ *   The number of objects dequeued, either 0 or n
+ */
+static __rte_always_inline unsigned int
+rte_st_ring_dequeue_bulk(struct rte_st_ring *r, void **obj_table, unsigned int n,
+		unsigned int *available)
+{
+	return rte_st_ring_dequeue_bulk_elem(r, obj_table, sizeof(void *),
+			n, available);
+}
+
+/**
+ * Dequeue upto a maximum number of objects from a ST ring.
+ *
+ * This function copies the objects from the tail of the ring and
+ * moves the tail index.
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @param obj_table
+ *   A pointer to a table of void * pointers (objects) that will be filled.
+ * @param n
+ *   The number of objects to dequeue from the ring to the obj_table.
+ * @param available
+ *   If non-NULL, returns the number of remaining ring entries after the
+ *   dequeue has finished.
+ * @return
+ *   - Number of objects dequeued
+ */
+static __rte_always_inline unsigned int
+rte_st_ring_dequeue_burst(struct rte_st_ring *r, void **obj_table,
+		unsigned int n, unsigned int *available)
+{
+	return rte_st_ring_dequeue_burst_elem(r, obj_table, sizeof(void *),
+			n, available);
+}
+
+/**
+ * Dequeue one object from a ST ring.
+ *
+ * This function copies one object from the tail of the ring and
+ * moves the tail index.
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @param obj_p
+ *   A pointer to a void * pointer (object) that will be filled.
+ * @return
+ *   - 0: Success, objects dequeued.
+ *   - -ENOENT: Not enough entries in the ring to dequeue, no object is
+ *     dequeued.
+ */
+static __rte_always_inline int
+rte_st_ring_dequeue(struct rte_st_ring *r, void **obj_p)
+{
+	return rte_st_ring_dequeue_elem(r, obj_p, sizeof(void *));
+}
+
+/**
+ * Dequeue a fixed number of objects from a ST ring from the head.
+ *
+ * This function copies the objects from the head of the ring and
+ * moves the head index (backwards).
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @param obj_table
+ *   A pointer to a table of void * pointers (objects) that will be filled.
+ * @param n
+ *   The number of objects to dequeue from the ring to the obj_table.
+ * @param available
+ *   If non-NULL, returns the number of remaining ring entries after the
+ *   dequeue has finished.
+ * @return
+ *   The number of objects dequeued, either 0 or n
+ */
+static __rte_always_inline unsigned int
+rte_st_ring_dequeue_at_head_bulk(struct rte_st_ring *r, void **obj_table, unsigned int n,
+		unsigned int *available)
+{
+	return rte_st_ring_dequeue_bulk_elem(r, obj_table, sizeof(void *),
+			n, available);
+}
+
+/**
+ * Dequeue upto a maximum number of objects from a ST ring from the head.
+ *
+ * This function copies the objects from the head of the ring and
+ * moves the head index (backwards).
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @param obj_table
+ *   A pointer to a table of void * pointers (objects) that will be filled.
+ * @param n
+ *   The number of objects to dequeue from the ring to the obj_table.
+ * @param available
+ *   If non-NULL, returns the number of remaining ring entries after the
+ *   dequeue has finished.
+ * @return
+ *   - Number of objects dequeued
+ */
+static __rte_always_inline unsigned int
+rte_st_ring_dequeue_at_head_burst(struct rte_st_ring *r, void **obj_table,
+		unsigned int n, unsigned int *available)
+{
+	return rte_st_ring_dequeue_burst_elem(r, obj_table, sizeof(void *),
+			n, available);
+}
+
+/**
+ * Dequeue one object from a ST ring from the head.
+ *
+ * This function copies the objects from the head of the ring and
+ * moves the head index (backwards).
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @param obj_p
+ *   A pointer to a void * pointer (object) that will be filled.
+ * @return
+ *   - 0: Success, objects dequeued.
+ *   - -ENOENT: Not enough entries in the ring to dequeue, no object is
+ *     dequeued.
+ */
+static __rte_always_inline int
+rte_st_ring_at_head_dequeue(struct rte_st_ring *r, void **obj_p)
+{
+	return rte_st_ring_dequeue_elem(r, obj_p, sizeof(void *));
+}
+
+/**
+ * Flush a ST ring.
+ *
+ * This function flush all the elements in a ST ring
+ *
+ * @warning
+ * Make sure the ring is not in use while calling this function.
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ */
+void
+rte_st_ring_reset(struct rte_st_ring *r);
+
+/**
+ * Return the number of entries in a ST ring.
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @return
+ *   The number of entries in the ring.
+ */
+static inline unsigned int
+rte_st_ring_count(const struct rte_st_ring *r)
+{
+	uint32_t count = (r->head - r->tail) & r->mask;
+	return count;
+}
+
+/**
+ * Return the number of free entries in a ST ring.
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @return
+ *   The number of free entries in the ring.
+ */
+static inline unsigned int
+rte_st_ring_free_count(const struct rte_st_ring *r)
+{
+	return r->capacity - rte_st_ring_count(r);
+}
+
+/**
+ * Test if a ST ring is full.
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @return
+ *   - 1: The ring is full.
+ *   - 0: The ring is not full.
+ */
+static inline int
+rte_st_ring_full(const struct rte_st_ring *r)
+{
+	return rte_st_ring_free_count(r) == 0;
+}
+
+/**
+ * Test if a ST ring is empty.
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @return
+ *   - 1: The ring is empty.
+ *   - 0: The ring is not empty.
+ */
+static inline int
+rte_st_ring_empty(const struct rte_st_ring *r)
+{
+	return r->tail == r->head;
+}
+
+/**
+ * Return the size of the ring.
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @return
+ *   The size of the data store used by the ring.
+ *   NOTE: this is not the same as the usable space in the ring. To query that
+ *   use ``rte_st_ring_get_capacity()``.
+ */
+static inline unsigned int
+rte_st_ring_get_size(const struct rte_st_ring *r)
+{
+	return r->size;
+}
+
+/**
+ * Return the number of elements which can be stored in the ring.
+ *
+ * @param r
+ *   A pointer to the ring structure.
+ * @return
+ *   The usable size of the ring.
+ */
+static inline unsigned int
+rte_st_ring_get_capacity(const struct rte_st_ring *r)
+{
+	return r->capacity;
+}
+
+/**
+ * Dump the status of all rings on the console
+ *
+ * @param f
+ *   A pointer to a file for output
+ */
+void rte_st_ring_list_dump(FILE *f);
+
+/**
+ * Search a ST ring from its name
+ *
+ * @param name
+ *   The name of the ring.
+ * @return
+ *   The pointer to the ring matching the name, or NULL if not found,
+ *   with rte_errno set appropriately. Possible rte_errno values include:
+ *    - ENOENT - required entry not available to return.
+ */
+struct rte_st_ring *rte_st_ring_lookup(const char *name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_ST_RING_H_ */