[PATCH RFC v2 6/8] SUNRPC: Reduce rpciod workqueue contention

From: Chuck Lever

Date: Wed Sep 02 2026 - 15:47:22 EST


rpciod drives the RPC client state machine. Under heavy NFS
workloads, multiple CPUs queue RPC task completions concurrently and
contend on the UNBOUND worker pool lock. perf profiles on a 12-core
system show 30-40% of cycles lost to
native_queued_spin_lock_slowpath in the rpciod pool at the
WQ_AFFN_CACHE scope (one pool per LLC). The WQ_AFFN_CACHE_SHARD
default helps little here, because its 8-core shards split this
system into just two pools of six cores each.

Set WQ_AFFN_SMT on rpciod so each SMT group gets its own pool and
lock. Most UNBOUND workqueues never contend on the pool lock and
profit from a coarser scope's cache locality. rpciod's sustained
completion traffic makes the lock a first-order bottleneck, so the
override belongs on this workqueue rather than in the system-wide
default. The cost is one pool per SMT group, or per CPU on a system
without SMT, and each pool keeps up to two idle kworkers rather than
culling its last ones.

Suggested-by: Tejun Heo <tj@xxxxxxxxxx>
Signed-off-by: Chuck Lever <cel@xxxxxxxxxx>
---
net/sunrpc/sched.c | 12 ++++++++++++
1 file changed, 12 insertions(+)

diff --git a/net/sunrpc/sched.c b/net/sunrpc/sched.c
index e81419aa553c..2a1938b9e41e 100644
--- a/net/sunrpc/sched.c
+++ b/net/sunrpc/sched.c
@@ -1268,6 +1268,17 @@ void rpciod_down(void)
module_put(THIS_MODULE);
}

+static void rpc_set_wq_smt_affinity(struct workqueue_struct *wq,
+ const char *name)
+{
+ int err;
+
+ err = workqueue_set_affn_scope(wq, WQ_AFFN_SMT);
+ if (err)
+ pr_warn("%s: failed to set SMT affinity scope: %d\n",
+ name, err);
+}
+
/*
* Start up the rpciod workqueue.
*/
@@ -1282,6 +1293,7 @@ static int rpciod_start(void)
wq = alloc_workqueue("rpciod", wq_flags, 0);
if (!wq)
goto out_failed;
+ rpc_set_wq_smt_affinity(wq, "rpciod");
rpciod_workqueue = wq;
wq = alloc_workqueue("xprtiod", wq_flags, 0);
if (!wq)

--
2.55.0