Skip to content

Commit ce153d2

Browse files
bsberndhbirth
authored andcommitted
fuse: {io-uring} Distribute load among queues
So far queue selection was statically - the a request on core X was always handled by the queue corresponding to core X. A previous commit introduced bitmaps that track which queues are available - queue selection can make use of these bitmaps and try to use the ideal queue. Rules are - Tries the queue of the current core first, if available and if that queue does not have too many entries queued - Then tries the first available queue on the current numa node. It does not use random distribution here, because light queue usage is probably ok, so that kernel/userspace switches can be avoided - Then tries the first available queue on the current numa node If no queue is free, it tries again the queue of the current core, but if that queue does not exist it falls back to a random queue on the current numa node - that also might not exist - it then uses a random available queue. Signed-off-by: Bernd Schubert <bschubert@ddn.com> (cherry picked from commit a595776)
1 parent c1fc280 commit ce153d2

1 file changed

Lines changed: 130 additions & 69 deletions

File tree

fs/fuse/dev_uring.c

Lines changed: 130 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,15 @@ static inline void io_uring_cmd_private_sz_check(size_t cmd_sz)
4343
)
4444
#endif
4545

46-
/* Number of queued fuse requests until a queue is considered full */
47-
#define FUSE_URING_QUEUE_THRESHOLD 5
46+
/* Number of queued fuse requests until a queue is considered full
47+
* Basically no entries, as synchronization is with bitmaps and lockless. I.e.
48+
* no accuracy - queues always get a bit more requests that way. Lightly
49+
* loaded queues is wanted to reduced kernel/userspace switches.
50+
*/
51+
#define FUSE_URING_QUEUE_THRESHOLD 0
52+
53+
static unsigned int fuse_uring_get_random_qid(struct fuse_ring *ring,
54+
const struct cpumask *mask);
4855

4956
bool fuse_uring_enabled(void)
5057
{
@@ -1049,8 +1056,7 @@ static void fuse_uring_ent_avail(struct fuse_ring_ent *ent,
10491056
list_move(&ent->list, &queue->ent_avail_queue);
10501057
ent->state = FRRS_AVAILABLE;
10511058

1052-
if (list_is_singular(&queue->ent_avail_queue) &&
1053-
queue->nr_reqs <= FUSE_URING_QUEUE_THRESHOLD) {
1059+
if (queue->nr_reqs <= FUSE_URING_QUEUE_THRESHOLD) {
10541060
cpumask_set_cpu(queue->qid, ring->avail_q_mask);
10551061
cpumask_set_cpu(queue->qid, ring->per_numa_avail_q_mask[node]);
10561062
}
@@ -1594,80 +1600,109 @@ static void fuse_uring_send_in_task(struct io_uring_cmd *cmd,
15941600
fuse_uring_send(ent, cmd, err, issue_flags);
15951601
}
15961602

1597-
static struct fuse_ring_queue *fuse_uring_select_queue(struct fuse_ring *ring,
1598-
bool background)
1603+
static struct fuse_ring_queue *
1604+
fuse_uring_get_first_queue(struct fuse_ring *ring, const struct cpumask *mask)
15991605
{
1600-
unsigned int qid;
1601-
int node, tries = 0;
1602-
unsigned int nr_queues;
1603-
unsigned int cpu = task_cpu(current);
1604-
struct fuse_ring_queue *queue, *primary_queue = NULL;
1606+
int qid;
16051607

1606-
/*
1607-
* Background requests result in better performance on a different
1608-
* CPU, unless CPUs are already busy.
1609-
*/
1610-
if (background)
1611-
cpu++;
1608+
/* Find the first available CPU in this mask */
1609+
qid = cpumask_first(mask);
16121610

1613-
retry:
1614-
cpu = cpu % ring->max_nr_queues;
1615-
1616-
/* numa local registered queue bitmap */
1617-
node = cpu_to_node(cpu);
1618-
if (WARN_ONCE(node >= ring->nr_numa_nodes,
1619-
"Node number (%d) exceeds nr nodes (%d)\n",
1620-
node, ring->nr_numa_nodes)) {
1621-
node = 0;
1622-
}
1623-
1624-
nr_queues = READ_ONCE(ring->numa_q_map[node].nr_queues);
1625-
if (nr_queues) {
1626-
/* prefer the queue that corresponds to the current cpu */
1627-
queue = READ_ONCE(ring->queues[cpu]);
1628-
if (queue) {
1629-
if (queue->nr_reqs <= FUSE_URING_Q_THRESHOLD)
1630-
return queue;
1631-
primary_queue = queue;
1632-
}
1611+
/* Check if we found a valid CPU */
1612+
if (qid >= ring->max_nr_queues)
1613+
return NULL; /* No available queues */
1614+
1615+
/* This is the global mask, cpu is already the global qid */
1616+
return ring->queues[qid];
1617+
}
16331618

1634-
qid = ring->numa_q_map[node].cpu_to_qid[cpu];
1635-
if (WARN_ON_ONCE(qid >= ring->max_nr_queues))
1636-
return NULL;
1637-
if (qid != cpu) {
1638-
queue = READ_ONCE(ring->queues[qid]);
1619+
/*
1620+
* Return a random queue from the registered queues mask
1621+
*
1622+
* Uses a deterministic but well-distributed algorithm to select
1623+
* a random queue from the provided CPU mask.
1624+
*/
1625+
static unsigned int fuse_uring_get_random_qid(struct fuse_ring *ring,
1626+
const struct cpumask *mask)
1627+
{
1628+
unsigned int nr_bits = cpumask_weight(mask);
1629+
unsigned int nth, cpu;
16391630

1640-
/* Might happen on teardown */
1641-
if (unlikely(!queue))
1642-
return NULL;
1631+
if (nr_bits == 0)
1632+
return UINT_MAX;
16431633

1644-
if (queue->nr_reqs <= FUSE_URING_Q_THRESHOLD)
1645-
return queue;
1646-
}
1634+
/* Fast path for single CPU */
1635+
if (nr_bits == 1)
1636+
return cpumask_first(mask);
16471637

1648-
/* Retries help for load balancing */
1649-
if (tries < FUSE_URING_Q_TRIES && tries + 1 < nr_queues) {
1650-
if (!primary_queue)
1651-
primary_queue = queue;
1638+
/*
1639+
* Use current jiffies and task PID to create a pseudo-random
1640+
* but well-distributed selection that varies across calls
1641+
*/
1642+
nth = (get_random_u32() ^ (jiffies & 0xFFFF) ^
1643+
(current->pid & 0xFFFF)) %
1644+
nr_bits;
16521645

1653-
/* Increase cpu, assuming it will map to a different qid*/
1654-
cpu++;
1655-
tries++;
1656-
goto retry;
1657-
}
1646+
/* Find the CPU at that position */
1647+
for_each_cpu(cpu, mask) {
1648+
if (nth-- == 0)
1649+
return cpu;
16581650
}
16591651

1660-
/* Retries exceeded, take the primary target queue */
1661-
if (primary_queue)
1662-
return primary_queue;
1652+
return UINT_MAX;
1653+
}
16631654

1664-
/* global registered queue bitmap */
1665-
qid = ring->q_map.cpu_to_qid[cpu];
1666-
if (WARN_ON_ONCE(qid >= ring->max_nr_queues)) {
1667-
/* Might happen on teardown */
1668-
return NULL;
1669-
}
1670-
return READ_ONCE(ring->queues[qid]);
1655+
/*
1656+
* Get the best queue for the current CPU
1657+
*/
1658+
static struct fuse_ring_queue *fuse_uring_get_queue(struct fuse_ring *ring)
1659+
{
1660+
unsigned int qid;
1661+
struct fuse_ring_queue *queue, *local_queue = NULL;
1662+
int local_node;
1663+
struct cpumask *mask;
1664+
struct fuse_conn *fc = ring->fc;
1665+
1666+
qid = task_cpu(current);
1667+
local_node = cpu_to_node(qid);
1668+
if (WARN_ON_ONCE(local_node >= ring->nr_numa_nodes || local_node < 0))
1669+
local_node = 0;
1670+
1671+
/* First check if current CPU's queue is available */
1672+
if (qid < ring->max_nr_queues) {
1673+
local_queue = queue = ring->queues[qid];
1674+
if (queue && queue->nr_reqs <= FUSE_URING_QUEUE_THRESHOLD)
1675+
return queue;
1676+
}
1677+
1678+
/* Second check if there are any available queues on the local node */
1679+
mask = ring->per_numa_avail_q_mask[local_node];
1680+
queue = fuse_uring_get_first_queue(ring, mask);
1681+
if (queue)
1682+
return queue;
1683+
1684+
/* Third check if there are any available queues on any node */
1685+
queue = fuse_uring_get_first_queue(ring, ring->avail_q_mask);
1686+
if (queue)
1687+
return queue;
1688+
1689+
/* No free queue, use the local queue if it exists */
1690+
if (local_queue)
1691+
return local_queue;
1692+
1693+
/* Try to use a random queue from the local NUMA node, if there is one */
1694+
mask = ring->numa_registered_q_mask[local_node];
1695+
qid = fuse_uring_get_random_qid(ring, mask);
1696+
if (qid < ring->max_nr_queues)
1697+
return ring->queues[qid];
1698+
1699+
/* Finally, use a random queue among all queues that are registered */
1700+
qid = fuse_uring_get_random_qid(ring, ring->registered_q_mask);
1701+
if (qid < ring->max_nr_queues)
1702+
return ring->queues[qid];
1703+
1704+
WARN_ON_ONCE(fc->connected);
1705+
return NULL;
16711706
}
16721707

16731708
static void fuse_uring_dispatch_ent(struct fuse_ring_ent *ent, bool bg)
@@ -1707,7 +1742,7 @@ void fuse_uring_queue_fuse_req(struct fuse_iqueue *fiq, struct fuse_req *req)
17071742
int err;
17081743

17091744
err = -EINVAL;
1710-
queue = fuse_uring_select_queue(ring, false);
1745+
queue = fuse_uring_get_queue(ring);
17111746
if (!queue)
17121747
goto err;
17131748

@@ -1722,6 +1757,19 @@ void fuse_uring_queue_fuse_req(struct fuse_iqueue *fiq, struct fuse_req *req)
17221757
struct fuse_ring_ent, list);
17231758
queue->nr_reqs++;
17241759

1760+
/*
1761+
* Update queue availability based on number of requests
1762+
* A queue is considered busy if it has more than
1763+
* FUSE_URING_QUEUE_THRESHOLD requests
1764+
*/
1765+
if (queue->nr_reqs == FUSE_URING_QUEUE_THRESHOLD + 1) {
1766+
/* Queue just became busy */
1767+
cpumask_clear_cpu(queue->qid, ring->avail_q_mask);
1768+
cpumask_clear_cpu(
1769+
queue->qid,
1770+
ring->per_numa_avail_q_mask[queue->numa_node]);
1771+
}
1772+
17251773
if (ent)
17261774
fuse_uring_add_req_to_ring_ent(ent, req);
17271775
else
@@ -1749,7 +1797,7 @@ bool fuse_uring_queue_bq_req(struct fuse_req *req)
17491797
struct fuse_ring_queue *queue;
17501798
struct fuse_ring_ent *ent = NULL;
17511799

1752-
queue = fuse_uring_select_queue(ring, true);
1800+
queue = fuse_uring_get_queue(ring);
17531801
if (!queue)
17541802
return false;
17551803

@@ -1795,12 +1843,25 @@ bool fuse_uring_queue_bq_req(struct fuse_req *req)
17951843
bool fuse_uring_remove_pending_req(struct fuse_req *req)
17961844
{
17971845
struct fuse_ring_queue *queue = req->ring_queue;
1846+
struct fuse_ring *ring = queue->ring;
1847+
int node = queue->numa_node;
17981848
bool removed = fuse_remove_pending_req(req, &queue->lock);
17991849

18001850
if (removed) {
18011851
/* Update counters after successful removal */
18021852
spin_lock(&queue->lock);
18031853
queue->nr_reqs--;
1854+
1855+
/*
1856+
* Update queue availability based on number of requests
1857+
* A queue is considered available if it has FUSE_URING_QUEUE_THRESHOLD or fewer requests
1858+
*/
1859+
if (queue->nr_reqs == FUSE_URING_QUEUE_THRESHOLD) {
1860+
/* Queue just became available */
1861+
cpumask_set_cpu(queue->qid, ring->avail_q_mask);
1862+
cpumask_set_cpu(queue->qid,
1863+
ring->per_numa_avail_q_mask[node]);
1864+
}
18041865
spin_unlock(&queue->lock);
18051866
}
18061867

0 commit comments

Comments
 (0)