stack: add lock-free implementation
This commit adds support for a lock-free (linked list based) stack to the
stack API. This behavior is selected through a new rte_stack_create() flag,
RTE_STACK_F_LF.
The stack consists of a linked list of elements, each containing a data
pointer and a next pointer, and an atomic stack depth counter.
The lock-free push operation enqueues a linked list of pointers by pointing
the tail of the list to the current stack head, and using a CAS to swing
the stack head pointer to the head of the list. The operation retries if it
is unsuccessful (i.e. the list changed between reading the head and
modifying it), else it adjusts the stack length and returns.
The lock-free pop operation first reserves num elements by adjusting the
stack length, to ensure the dequeue operation will succeed without
blocking. It then dequeues pointers by walking the list -- starting from
the head -- then swinging the head pointer (using a CAS as well). While
walking the list, the data pointers are recorded in an object table.
This algorithm stack uses a 128-bit compare-and-swap instruction, which
atomically updates the stack top pointer and a modification counter, to
protect against the ABA problem.
The linked list elements themselves are maintained in a lock-free LIFO
list, and are allocated before stack pushes and freed after stack pops.
Since the stack has a fixed maximum depth, these elements do not need to be
dynamically created.
Signed-off-by: Gage Eads <gage.eads@intel.com>
Reviewed-by: Olivier Matz <olivier.matz@6wind.com>
Reviewed-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
2019-04-03 18:20:17 -05:00
|
|
|
/* SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
* Copyright(c) 2019 Intel Corporation
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef _RTE_STACK_LF_GENERIC_H_
|
|
|
|
#define _RTE_STACK_LF_GENERIC_H_
|
|
|
|
|
|
|
|
#include <rte_branch_prediction.h>
|
|
|
|
#include <rte_prefetch.h>
|
|
|
|
|
|
|
|
static __rte_always_inline unsigned int
|
|
|
|
__rte_stack_lf_count(struct rte_stack *s)
|
|
|
|
{
|
|
|
|
/* stack_lf_push() and stack_lf_pop() do not update the list's contents
|
|
|
|
* and stack_lf->len atomically, which can cause the list to appear
|
|
|
|
* shorter than it actually is if this function is called while other
|
|
|
|
* threads are modifying the list.
|
|
|
|
*
|
|
|
|
* However, given the inherently approximate nature of the get_count
|
|
|
|
* callback -- even if the list and its size were updated atomically,
|
|
|
|
* the size could change between when get_count executes and when the
|
|
|
|
* value is returned to the caller -- this is acceptable.
|
|
|
|
*
|
|
|
|
* The stack_lf->len updates are placed such that the list may appear to
|
|
|
|
* have fewer elements than it does, but will never appear to have more
|
|
|
|
* elements. If the mempool is near-empty to the point that this is a
|
|
|
|
* concern, the user should consider increasing the mempool size.
|
|
|
|
*/
|
2019-06-17 15:41:30 +08:00
|
|
|
return (unsigned int)rte_atomic64_read((rte_atomic64_t *)
|
|
|
|
&s->stack_lf.used.len);
|
stack: add lock-free implementation
This commit adds support for a lock-free (linked list based) stack to the
stack API. This behavior is selected through a new rte_stack_create() flag,
RTE_STACK_F_LF.
The stack consists of a linked list of elements, each containing a data
pointer and a next pointer, and an atomic stack depth counter.
The lock-free push operation enqueues a linked list of pointers by pointing
the tail of the list to the current stack head, and using a CAS to swing
the stack head pointer to the head of the list. The operation retries if it
is unsuccessful (i.e. the list changed between reading the head and
modifying it), else it adjusts the stack length and returns.
The lock-free pop operation first reserves num elements by adjusting the
stack length, to ensure the dequeue operation will succeed without
blocking. It then dequeues pointers by walking the list -- starting from
the head -- then swinging the head pointer (using a CAS as well). While
walking the list, the data pointers are recorded in an object table.
This algorithm stack uses a 128-bit compare-and-swap instruction, which
atomically updates the stack top pointer and a modification counter, to
protect against the ABA problem.
The linked list elements themselves are maintained in a lock-free LIFO
list, and are allocated before stack pushes and freed after stack pops.
Since the stack has a fixed maximum depth, these elements do not need to be
dynamically created.
Signed-off-by: Gage Eads <gage.eads@intel.com>
Reviewed-by: Olivier Matz <olivier.matz@6wind.com>
Reviewed-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
2019-04-03 18:20:17 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
static __rte_always_inline void
|
|
|
|
__rte_stack_lf_push_elems(struct rte_stack_lf_list *list,
|
|
|
|
struct rte_stack_lf_elem *first,
|
|
|
|
struct rte_stack_lf_elem *last,
|
|
|
|
unsigned int num)
|
|
|
|
{
|
|
|
|
#ifndef RTE_ARCH_X86_64
|
|
|
|
RTE_SET_USED(first);
|
|
|
|
RTE_SET_USED(last);
|
|
|
|
RTE_SET_USED(list);
|
|
|
|
RTE_SET_USED(num);
|
|
|
|
#else
|
|
|
|
struct rte_stack_lf_head old_head;
|
|
|
|
int success;
|
|
|
|
|
|
|
|
old_head = list->head;
|
|
|
|
|
|
|
|
do {
|
|
|
|
struct rte_stack_lf_head new_head;
|
|
|
|
|
|
|
|
/* An acquire fence (or stronger) is needed for weak memory
|
|
|
|
* models to establish a synchronized-with relationship between
|
|
|
|
* the list->head load and store-release operations (as part of
|
|
|
|
* the rte_atomic128_cmp_exchange()).
|
|
|
|
*/
|
|
|
|
rte_smp_mb();
|
|
|
|
|
|
|
|
/* Swing the top pointer to the first element in the list and
|
|
|
|
* make the last element point to the old top.
|
|
|
|
*/
|
|
|
|
new_head.top = first;
|
|
|
|
new_head.cnt = old_head.cnt + 1;
|
|
|
|
|
|
|
|
last->next = old_head.top;
|
|
|
|
|
|
|
|
/* old_head is updated on failure */
|
|
|
|
success = rte_atomic128_cmp_exchange(
|
|
|
|
(rte_int128_t *)&list->head,
|
|
|
|
(rte_int128_t *)&old_head,
|
|
|
|
(rte_int128_t *)&new_head,
|
|
|
|
1, __ATOMIC_RELEASE,
|
|
|
|
__ATOMIC_RELAXED);
|
|
|
|
} while (success == 0);
|
|
|
|
|
2019-06-17 15:41:30 +08:00
|
|
|
rte_atomic64_add((rte_atomic64_t *)&list->len, num);
|
stack: add lock-free implementation
This commit adds support for a lock-free (linked list based) stack to the
stack API. This behavior is selected through a new rte_stack_create() flag,
RTE_STACK_F_LF.
The stack consists of a linked list of elements, each containing a data
pointer and a next pointer, and an atomic stack depth counter.
The lock-free push operation enqueues a linked list of pointers by pointing
the tail of the list to the current stack head, and using a CAS to swing
the stack head pointer to the head of the list. The operation retries if it
is unsuccessful (i.e. the list changed between reading the head and
modifying it), else it adjusts the stack length and returns.
The lock-free pop operation first reserves num elements by adjusting the
stack length, to ensure the dequeue operation will succeed without
blocking. It then dequeues pointers by walking the list -- starting from
the head -- then swinging the head pointer (using a CAS as well). While
walking the list, the data pointers are recorded in an object table.
This algorithm stack uses a 128-bit compare-and-swap instruction, which
atomically updates the stack top pointer and a modification counter, to
protect against the ABA problem.
The linked list elements themselves are maintained in a lock-free LIFO
list, and are allocated before stack pushes and freed after stack pops.
Since the stack has a fixed maximum depth, these elements do not need to be
dynamically created.
Signed-off-by: Gage Eads <gage.eads@intel.com>
Reviewed-by: Olivier Matz <olivier.matz@6wind.com>
Reviewed-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
2019-04-03 18:20:17 -05:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static __rte_always_inline struct rte_stack_lf_elem *
|
|
|
|
__rte_stack_lf_pop_elems(struct rte_stack_lf_list *list,
|
|
|
|
unsigned int num,
|
|
|
|
void **obj_table,
|
|
|
|
struct rte_stack_lf_elem **last)
|
|
|
|
{
|
|
|
|
#ifndef RTE_ARCH_X86_64
|
|
|
|
RTE_SET_USED(obj_table);
|
|
|
|
RTE_SET_USED(last);
|
|
|
|
RTE_SET_USED(list);
|
|
|
|
RTE_SET_USED(num);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
#else
|
|
|
|
struct rte_stack_lf_head old_head;
|
|
|
|
int success;
|
|
|
|
|
|
|
|
/* Reserve num elements, if available */
|
|
|
|
while (1) {
|
2019-06-17 15:41:30 +08:00
|
|
|
uint64_t len = rte_atomic64_read((rte_atomic64_t *)&list->len);
|
stack: add lock-free implementation
This commit adds support for a lock-free (linked list based) stack to the
stack API. This behavior is selected through a new rte_stack_create() flag,
RTE_STACK_F_LF.
The stack consists of a linked list of elements, each containing a data
pointer and a next pointer, and an atomic stack depth counter.
The lock-free push operation enqueues a linked list of pointers by pointing
the tail of the list to the current stack head, and using a CAS to swing
the stack head pointer to the head of the list. The operation retries if it
is unsuccessful (i.e. the list changed between reading the head and
modifying it), else it adjusts the stack length and returns.
The lock-free pop operation first reserves num elements by adjusting the
stack length, to ensure the dequeue operation will succeed without
blocking. It then dequeues pointers by walking the list -- starting from
the head -- then swinging the head pointer (using a CAS as well). While
walking the list, the data pointers are recorded in an object table.
This algorithm stack uses a 128-bit compare-and-swap instruction, which
atomically updates the stack top pointer and a modification counter, to
protect against the ABA problem.
The linked list elements themselves are maintained in a lock-free LIFO
list, and are allocated before stack pushes and freed after stack pops.
Since the stack has a fixed maximum depth, these elements do not need to be
dynamically created.
Signed-off-by: Gage Eads <gage.eads@intel.com>
Reviewed-by: Olivier Matz <olivier.matz@6wind.com>
Reviewed-by: Honnappa Nagarahalli <honnappa.nagarahalli@arm.com>
2019-04-03 18:20:17 -05:00
|
|
|
|
|
|
|
/* Does the list contain enough elements? */
|
|
|
|
if (unlikely(len < num))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (rte_atomic64_cmpset((volatile uint64_t *)&list->len,
|
|
|
|
len, len - num))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
old_head = list->head;
|
|
|
|
|
|
|
|
/* Pop num elements */
|
|
|
|
do {
|
|
|
|
struct rte_stack_lf_head new_head;
|
|
|
|
struct rte_stack_lf_elem *tmp;
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
/* An acquire fence (or stronger) is needed for weak memory
|
|
|
|
* models to ensure the LF LIFO element reads are properly
|
|
|
|
* ordered with respect to the head pointer read.
|
|
|
|
*/
|
|
|
|
rte_smp_mb();
|
|
|
|
|
|
|
|
rte_prefetch0(old_head.top);
|
|
|
|
|
|
|
|
tmp = old_head.top;
|
|
|
|
|
|
|
|
/* Traverse the list to find the new head. A next pointer will
|
|
|
|
* either point to another element or NULL; if a thread
|
|
|
|
* encounters a pointer that has already been popped, the CAS
|
|
|
|
* will fail.
|
|
|
|
*/
|
|
|
|
for (i = 0; i < num && tmp != NULL; i++) {
|
|
|
|
rte_prefetch0(tmp->next);
|
|
|
|
if (obj_table)
|
|
|
|
obj_table[i] = tmp->data;
|
|
|
|
if (last)
|
|
|
|
*last = tmp;
|
|
|
|
tmp = tmp->next;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If NULL was encountered, the list was modified while
|
|
|
|
* traversing it. Retry.
|
|
|
|
*/
|
|
|
|
if (i != num)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
new_head.top = tmp;
|
|
|
|
new_head.cnt = old_head.cnt + 1;
|
|
|
|
|
|
|
|
/* old_head is updated on failure */
|
|
|
|
success = rte_atomic128_cmp_exchange(
|
|
|
|
(rte_int128_t *)&list->head,
|
|
|
|
(rte_int128_t *)&old_head,
|
|
|
|
(rte_int128_t *)&new_head,
|
|
|
|
1, __ATOMIC_RELEASE,
|
|
|
|
__ATOMIC_RELAXED);
|
|
|
|
} while (success == 0);
|
|
|
|
|
|
|
|
return old_head.top;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* _RTE_STACK_LF_GENERIC_H_ */
|