From: Pravin Pathak <pravinpathakdev@gmail.com>
To: dev@dpdk.org
Cc: jerinj@marvell.com, mike.ximing.chen@intel.com,
bruce.richardson@intel.com, thomas@monjalon.net,
david.marchand@redhat.com, nipun.gupta@amd.com,
chenbox@nvidia.com, tirthendu.sarkar@intel.com,
Pravin Pathak <pravin.pathak.dev@gmail.com>
Subject: [PATCH v1 4/8] event/slb: add dynamic flow migration for atomic queues
Date: Fri, 6 Nov 2026 20:30:06 -0500 [thread overview]
Message-ID: <20261107013011.2443138-5-pravinpathakdev@gmail.com> (raw)
In-Reply-To: <20261107013011.2443138-1-pravinpathakdev@gmail.com>
From: Pravin Pathak <pravin.pathak.dev@gmail.com>
Add dynamic flow migration to automatically rebalance flows across
worker ports when an atomic queue's destination port becomes congested.
When consecutive enqueue attempts to a worker's private atomic ring fail
and exceed SLB_MIGRATION_LOAD_THRESHOLD, migration is initiated. The
PMD inspects the overloaded ring and selects the lightest active flow
not currently in migration cooldown.
To preserve strict per-flow event ordering, migration completes only
after all previously scheduled events for the source port have been
returned (q_enqueued == q_returned). The flow affinity is then remapped
to the least-loaded worker port, and any matching events retained in the
hold buffer are reassigned.
Signed-off-by: Pravin Pathak <pravin.pathak.dev@gmail.com>
---
drivers/event/slb/slb_evdev.c | 16 ++-
drivers/event/slb/slb_evdev.h | 20 ++-
drivers/event/slb/slb_event.c | 252 ++++++++++++++++++++++++++++++++--
3 files changed, 273 insertions(+), 15 deletions(-)
diff --git a/drivers/event/slb/slb_evdev.c b/drivers/event/slb/slb_evdev.c
index be0b3630f6..1916b7faa4 100644
--- a/drivers/event/slb/slb_evdev.c
+++ b/drivers/event/slb/slb_evdev.c
@@ -74,8 +74,13 @@ slb_port_setup(struct rte_eventdev *dev, uint8_t port_id,
.total_dequeues = 0,
.empty_dequeues = 0,
.pending_releases = 0,
+ .is_loaded = false,
.single_linked_q = NULL,
.single_linked_ring = NULL,
+ .flow_migrations = 0,
+ .migrations_in = 0,
+ .migrations_out = 0,
+ .last_migrated_time = 0,
};
port->remap_input_q = false;
@@ -208,8 +213,12 @@ slb_queue_setup(struct rte_eventdev *dev, uint8_t queue_id,
queue->atomic_state->next_port_rr = 0;
rte_spinlock_init(&queue->atomic_state->refill_lock);
queue->atomic_state->hold_count = 0;
+ queue->atomic_state->migration.in_progress = false;
+ queue->atomic_state->migrations_count = 0;
memset(queue->atomic_state->port_stats, 0,
sizeof(queue->atomic_state->port_stats));
+ memset(queue->atomic_state->flow_last_migrated, 0,
+ sizeof(queue->atomic_state->flow_last_migrated));
} else {
queue->atomic_state = NULL;
}
@@ -547,12 +556,16 @@ slb_stop(struct rte_eventdev *dev)
struct slb_queue *q = &slb->queues[qid];
if (q->schedule_type == RTE_SCHED_TYPE_ATOMIC && q->atomic_state != NULL) {
- for (int f = 0; f < SLB_ATOMIC_FLOWS; f++)
+ for (int f = 0; f < SLB_ATOMIC_FLOWS; f++) {
rte_atomic_store_explicit(
&q->atomic_state->flow_map.flows[f].port_id,
-1, rte_memory_order_relaxed);
+ q->atomic_state->flow_last_migrated[f] = 0;
+ }
+ q->atomic_state->migration.in_progress = false;
for (uint16_t pid = 0; pid < slb->num_ports; pid++) {
+ q->atomic_state->port_stats[pid].load_counter = 0;
if (q->per_port_rings[pid] != NULL)
slb_drain_ring(dev_id,
q->per_port_rings[pid],
@@ -568,6 +581,7 @@ slb_stop(struct rte_eventdev *dev)
struct slb_port *port = &slb->ports[i];
port->inflight_credits = 0;
port->credits_drained = false;
+ port->is_loaded = false;
for (int p = 0; p < SLB_MAX_PRIORITIES; p++)
port->next_q[p] = 0;
}
diff --git a/drivers/event/slb/slb_evdev.h b/drivers/event/slb/slb_evdev.h
index 7a70d5c94c..b1a6de8606 100644
--- a/drivers/event/slb/slb_evdev.h
+++ b/drivers/event/slb/slb_evdev.h
@@ -87,6 +87,8 @@ struct slb_reorder_entry {
#define SLB_ATOMIC_FLOWS 2048
#define SLB_ATOMIC_FLOWS_MASK (SLB_ATOMIC_FLOWS - 1)
#define SLB_HOLD_BUFFER_SIZE (SLB_MAX_PORT_DEQUEUE_DEPTH * 64)
+#define SLB_MIGRATION_LOAD_THRESHOLD 16
+#define SLB_MIGRATION_COOLDOWN_MS 500
/* Per-flow entry for atomic scheduling affinity */
struct slb_flow {
@@ -100,17 +102,28 @@ struct slb_flow_map {
/* Per-port counters for an atomic queue */
struct __rte_cache_aligned slb_atomic_port_stats {
+ uint32_t load_counter; /* Incremented when full, decremented when space available */
uint32_t flow_count; /* Number of active flows mapped to this port */
uint64_t q_enqueued; /* Events scheduled into this port's atomic port-Q */
uint64_t q_returned; /* Events returned (FORWARD/RELEASE) by this port */
};
+/* Active migration state for an atomic queue */
+struct slb_migration_state {
+ bool in_progress;
+ uint8_t port_id; /* Source port being drained */
+ uint32_t flow_idx; /* Flow hash index being migrated */
+};
+
/* Dynamically allocated state for atomic queues */
struct slb_atomic_state {
struct slb_flow_map flow_map;
uint32_t next_port_rr;
rte_spinlock_t refill_lock;
uint16_t hold_count;
+ struct slb_migration_state migration;
+ uint64_t migrations_count;
+ uint64_t flow_last_migrated[SLB_ATOMIC_FLOWS];
alignas(RTE_CACHE_LINE_SIZE)
struct slb_atomic_port_stats port_stats[SLB_MAX_PORTS];
uint8_t hold_ports[SLB_HOLD_BUFFER_SIZE];
@@ -132,7 +145,8 @@ struct __rte_cache_aligned slb_port {
bool implicit_release;
bool credits_drained;
bool remap_input_q;
- uint8_t pad_port[4];
+ bool is_loaded; /* Port marked loaded for atomic flow migration */
+ uint8_t pad_port[3];
struct rte_event_ring *single_linked_ring;
struct slb_evdev *slb;
@@ -149,6 +163,10 @@ struct __rte_cache_aligned slb_port {
uint16_t dequeue_depth;
uint16_t enqueue_depth;
struct slb_queue *single_linked_q;
+ uint64_t flow_migrations;
+ uint64_t migrations_in;
+ uint64_t migrations_out;
+ uint64_t last_migrated_time;
uint64_t enqueue_calls;
uint64_t new_enqueued;
uint64_t forward_enqueued;
diff --git a/drivers/event/slb/slb_event.c b/drivers/event/slb/slb_event.c
index 22f40d72d4..3a0f7f6fde 100644
--- a/drivers/event/slb/slb_event.c
+++ b/drivers/event/slb/slb_event.c
@@ -3,6 +3,7 @@
*/
#include <stdbool.h>
+#include <limits.h>
#include <rte_bitops.h>
#include <rte_cycles.h>
#include <rte_ring_elem.h>
@@ -163,12 +164,204 @@ slb_flow_hash(uint32_t flow_id)
return (flow_id * 2654435761U) & SLB_ATOMIC_FLOWS_MASK;
}
+static int32_t
+slb_find_lightest_flow_on_port(struct slb_queue *queue, uint8_t port_id,
+ uint64_t now, uint64_t cooldown_cycles)
+{
+ struct rte_event_ring *ering = queue->per_port_rings[port_id];
+ struct slb_atomic_state *astate = queue->atomic_state;
+ struct rte_ring *r;
+ uint32_t cons;
+ uint32_t prod;
+ uint32_t count;
+ uint16_t counts[SLB_ATOMIC_FLOWS];
+ int32_t best_flow_idx = -1;
+ uint32_t min_events = UINT32_MAX;
+
+ if (unlikely(ering == NULL || astate == NULL))
+ return -1;
+
+ if (astate->port_stats[port_id].flow_count <= 1)
+ return -1;
+
+ r = &ering->r;
+ cons = rte_atomic_load_explicit(&r->cons.tail, rte_memory_order_relaxed);
+ prod = rte_atomic_load_explicit(&r->prod.tail, rte_memory_order_relaxed);
+ count = prod - cons;
+
+ memset(counts, 0, sizeof(counts));
+ if (count > 0) {
+ struct rte_event *ring_events = (struct rte_event *)(r + 1);
+
+ for (uint32_t i = 0; i < count; i++) {
+ uint32_t slot = (cons + i) & r->mask;
+ uint32_t f_idx = slb_flow_hash(ring_events[slot].flow_id);
+
+ counts[f_idx]++;
+ }
+ }
+
+ for (uint32_t f = 0; f < SLB_ATOMIC_FLOWS; f++) {
+ if (rte_atomic_load_explicit(&astate->flow_map.flows[f].port_id,
+ rte_memory_order_relaxed) == (int32_t)port_id) {
+ if (astate->flow_last_migrated[f] != 0 &&
+ (now - astate->flow_last_migrated[f]) < cooldown_cycles)
+ continue;
+
+ if (counts[f] < min_events) {
+ min_events = counts[f];
+ best_flow_idx = (int32_t)f;
+ }
+ }
+ }
+
+ return best_flow_idx;
+}
+
+static void
+slb_check_and_trigger_migration(struct slb_evdev *slb, struct slb_queue *queue,
+ struct slb_atomic_state *astate, uint8_t full_port_id)
+{
+ struct slb_atomic_port_stats *pstats = &astate->port_stats[full_port_id];
+ uint64_t now;
+ uint64_t hz;
+ uint64_t cooldown_cycles;
+ int32_t flow_to_migrate;
+
+ pstats->load_counter++;
+ if (pstats->load_counter < SLB_MIGRATION_LOAD_THRESHOLD)
+ return;
+
+ if (astate->migration.in_progress)
+ return;
+
+ if (slb->ports[full_port_id].is_loaded)
+ return;
+
+ if (pstats->flow_count <= 1)
+ return;
+
+ now = rte_get_timer_cycles();
+ hz = rte_get_timer_hz();
+ cooldown_cycles = (hz * SLB_MIGRATION_COOLDOWN_MS) / 1000;
+
+ if (slb->ports[full_port_id].last_migrated_time != 0 &&
+ (now - slb->ports[full_port_id].last_migrated_time) < cooldown_cycles)
+ return;
+
+ flow_to_migrate = slb_find_lightest_flow_on_port(
+ queue, full_port_id, now, cooldown_cycles);
+ if (flow_to_migrate < 0)
+ return;
+
+ pstats->load_counter = 0;
+ slb->ports[full_port_id].is_loaded = true;
+ astate->migration.in_progress = true;
+ astate->migration.port_id = full_port_id;
+ astate->migration.flow_idx = (uint32_t)flow_to_migrate;
+}
+
+static inline int
+slb_find_migration_target_port(struct slb_evdev *slb, struct slb_queue *queue,
+ struct slb_atomic_state *astate, uint8_t mig_port,
+ uint64_t now, uint64_t cooldown_cycles)
+{
+ uint64_t temp_mask = queue->serving_ports_mask;
+ int target_port = -1;
+ int min_flows = INT_MAX;
+
+ while (temp_mask != 0) {
+ uint8_t cand_port = rte_ctz64(temp_mask);
+ struct slb_port *cp = &slb->ports[cand_port];
+
+ temp_mask &= ~(1ULL << cand_port);
+ if (cand_port == mig_port || cp->is_loaded)
+ continue;
+
+ if (cp->last_migrated_time == 0 ||
+ (now - cp->last_migrated_time) >= cooldown_cycles) {
+ if ((int)astate->port_stats[cand_port].flow_count < min_flows) {
+ min_flows = (int)astate->port_stats[cand_port].flow_count;
+ target_port = cand_port;
+ }
+ } else if (target_port == -1) {
+ target_port = cand_port;
+ }
+ }
+ return target_port;
+}
+
+static inline void
+slb_complete_flow_migration(struct slb_evdev *slb, struct slb_atomic_state *astate,
+ uint8_t mig_port, int target_port, uint64_t now)
+{
+ uint32_t f_idx = astate->migration.flow_idx;
+
+ rte_atomic_store_explicit(&astate->flow_map.flows[f_idx].port_id,
+ target_port, rte_memory_order_release);
+
+ for (uint16_t h = 0; h < astate->hold_count; h++) {
+ if (astate->hold_ports[h] == mig_port &&
+ slb_flow_hash(astate->hold_buffer[h].flow_id) == f_idx) {
+ astate->hold_ports[h] = (uint8_t)target_port;
+ }
+ }
+
+ astate->flow_last_migrated[f_idx] = now;
+ slb->ports[mig_port].last_migrated_time = now;
+ slb->ports[target_port].last_migrated_time = now;
+
+ if (astate->port_stats[mig_port].flow_count > 0)
+ astate->port_stats[mig_port].flow_count--;
+ astate->port_stats[target_port].flow_count++;
+ astate->migrations_count++;
+
+ slb->ports[mig_port].flow_migrations++;
+ slb->ports[mig_port].migrations_out++;
+ slb->ports[target_port].migrations_in++;
+
+ astate->port_stats[mig_port].load_counter = 0;
+ slb->ports[mig_port].is_loaded = false;
+ astate->migration.in_progress = false;
+}
+
+static inline uint8_t
+slb_assign_unmapped_flow_port(struct slb_evdev *slb, struct slb_atomic_state *astate,
+ uint64_t serving_mask, uint16_t num_serving_ports)
+{
+ uint32_t rr_counter = astate->next_port_rr++;
+ int port_offset = rr_counter % num_serving_ports;
+ uint64_t temp_mask = serving_mask;
+ uint8_t assigned_port = 0;
+
+ for (int j = 0; j <= port_offset; j++) {
+ assigned_port = rte_ctz64(temp_mask);
+ temp_mask &= (temp_mask - 1);
+ }
+
+ if (slb->ports[assigned_port].is_loaded) {
+ uint64_t cand_mask = serving_mask;
+
+ while (cand_mask != 0) {
+ uint8_t cp = rte_ctz64(cand_mask);
+
+ cand_mask &= (cand_mask - 1);
+ if (!slb->ports[cp].is_loaded) {
+ assigned_port = cp;
+ break;
+ }
+ }
+ }
+ return assigned_port;
+}
+
/* If port queue is empty for an atomic queue, pull events from main atomic queue to fill
* up port queues.
*/
static void
-slb_atomic_refill(struct slb_port *port __rte_unused, struct slb_queue *queue)
+slb_atomic_refill(struct slb_port *port, struct slb_queue *queue)
{
+ struct slb_evdev *slb = port->slb;
struct rte_event master_events[SLB_MAX_PORT_DEQUEUE_DEPTH];
uint16_t n_deq;
uint16_t i;
@@ -183,6 +376,35 @@ slb_atomic_refill(struct slb_port *port __rte_unused, struct slb_queue *queue)
if (!rte_spinlock_trylock(&astate->refill_lock))
return;
+ /* 0. Detect migration in progress before hold buffer processing */
+ if (astate->migration.in_progress) {
+ uint8_t mig_port = astate->migration.port_id;
+ uint64_t enq_cnt = astate->port_stats[mig_port].q_enqueued;
+ uint64_t ret_cnt = astate->port_stats[mig_port].q_returned;
+
+ if (enq_cnt == ret_cnt) {
+ uint64_t now = rte_get_timer_cycles();
+ uint64_t hz = rte_get_timer_hz();
+ uint64_t cooldown_cycles =
+ (hz * SLB_MIGRATION_COOLDOWN_MS) / 1000;
+ int target_port = slb_find_migration_target_port(
+ slb, queue, astate, mig_port, now,
+ cooldown_cycles);
+
+ if (target_port != -1)
+ slb_complete_flow_migration(
+ slb, astate, mig_port, target_port, now);
+ else {
+ astate->port_stats[mig_port].load_counter = 0;
+ slb->ports[mig_port].is_loaded = false;
+ astate->migration.in_progress = false;
+ }
+ }
+ }
+
+ if (astate->migration.in_progress)
+ full_ports_mask |= (1ULL << astate->migration.port_id);
+
/* 1. Process Hold Buffer first */
if (astate->hold_count > 0) {
uint16_t retained = 0;
@@ -216,6 +438,10 @@ slb_atomic_refill(struct slb_port *port __rte_unused, struct slb_queue *queue)
&astate->hold_buffer[i], burst_len, NULL);
astate->port_stats[dest_port_id].q_enqueued += enqueued;
+ if (enqueued > 0 &&
+ astate->port_stats[dest_port_id].load_counter > 0)
+ astate->port_stats[dest_port_id].load_counter--;
+
if (enqueued < burst_len) {
uint16_t rem = burst_len - enqueued;
uint16_t start_idx = i + enqueued;
@@ -230,6 +456,9 @@ slb_atomic_refill(struct slb_port *port __rte_unused, struct slb_queue *queue)
rem);
}
retained += rem;
+ if (enqueued == 0)
+ slb_check_and_trigger_migration(
+ slb, queue, astate, dest_port_id);
}
i += burst_len;
@@ -274,17 +503,8 @@ slb_atomic_refill(struct slb_port *port __rte_unused, struct slb_queue *queue)
rte_memory_order_acquire);
if (unlikely(dest_port_id == -1)) {
- uint32_t rr_counter = astate->next_port_rr++;
- int port_offset = rr_counter % num_serving_ports;
- uint64_t temp_mask = serving_mask;
- uint8_t assigned_port = 0;
-
- for (int j = 0; j <= port_offset; j++) {
- assigned_port = rte_ctz64(temp_mask);
- temp_mask &= (temp_mask - 1);
- }
-
- dest_port_id = assigned_port;
+ dest_port_id = slb_assign_unmapped_flow_port(
+ slb, astate, serving_mask, num_serving_ports);
rte_atomic_store_explicit(
&astate->flow_map.flows[flow_idx].port_id,
dest_port_id, rte_memory_order_release);
@@ -346,7 +566,10 @@ slb_atomic_refill(struct slb_port *port __rte_unused, struct slb_queue *queue)
queue->per_port_rings[p], &sorted_events[p_start], p_count, NULL);
astate->port_stats[p].q_enqueued += enqueued;
- if (enqueued < p_count) {
+ if (enqueued == p_count) {
+ if (unlikely(astate->port_stats[p].load_counter > 0))
+ astate->port_stats[p].load_counter--;
+ } else {
uint16_t rem = p_count - enqueued;
uint16_t space = SLB_HOLD_BUFFER_SIZE - astate->hold_count;
uint16_t to_copy = RTE_MIN(rem, space);
@@ -359,6 +582,9 @@ slb_atomic_refill(struct slb_port *port __rte_unused, struct slb_queue *queue)
memset(&astate->hold_ports[astate->hold_count], p, to_copy);
astate->hold_count += to_copy;
}
+ if (enqueued == 0)
+ slb_check_and_trigger_migration(
+ slb, queue, astate, p);
}
}
}
--
2.53.0
next prev parent reply other threads:[~2026-11-07 1:30 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-07 1:30 [PATCH v1 0/8] event/slb: introducing software load balancer eventdev PMD Pravin Pathak
2026-11-07 1:30 ` [PATCH v1 1/8] event/slb: introduce software load balancer PMD Pravin Pathak
2026-11-07 1:30 ` [PATCH v1 2/8] event/slb: add support for ordered queues Pravin Pathak
2026-11-07 1:30 ` [PATCH v1 3/8] event/slb: add support for atomic queues Pravin Pathak
2026-11-07 1:30 ` Pravin Pathak [this message]
2026-11-07 1:30 ` [PATCH v1 5/8] event/slb: add extended statistics and diagnostics Pravin Pathak
2026-11-07 1:30 ` [PATCH v1 6/8] event/slb: add self-test and documentation Pravin Pathak
2026-11-07 1:30 ` [PATCH v1 7/8] event/slb: update resets and credit defaults Pravin Pathak
2026-11-07 1:30 ` [PATCH v1 8/8] event/slb: improve port linking, config checks and atomic refill Pravin Pathak
2026-11-07 7:29 ` [PATCH v1 0/8] event/slb: introducing software load balancer eventdev PMD Jerin Jacob
2026-11-08 17:10 ` Stephen Hemminger
2026-11-09 2:30 ` Pravin Pathak
2026-11-09 12:55 ` Jerin Jacob
2026-11-11 14:22 ` Sarkar, Tirthendu
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261107013011.2443138-5-pravinpathakdev@gmail.com \
--to=pravinpathakdev@gmail.com \
--cc=bruce.richardson@intel.com \
--cc=chenbox@nvidia.com \
--cc=david.marchand@redhat.com \
--cc=dev@dpdk.org \
--cc=jerinj@marvell.com \
--cc=mike.ximing.chen@intel.com \
--cc=nipun.gupta@amd.com \
--cc=pravin.pathak.dev@gmail.com \
--cc=thomas@monjalon.net \
--cc=tirthendu.sarkar@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.