[PATCH RFC v2 7/8] NFS: Reduce nfsiod workqueue contention
From: Chuck Lever
Date: Wed Sep 02 2026 - 15:55:12 EST
The default affinity scope for unbound workqueues is now
WQ_AFFN_CACHE_SHARD, which splits each LLC into shards of about
eight cores. On a single-socket system whose LLC fits in one
shard, every NFS I/O completion serializes on one nfsiod pool
lock. Profiling 4KB random writes over NFSv3/RDMA with nconnect=3
shows that lock consuming 17% of CPU cycles: 8% dequeuing work and
9% enqueuing follow-on work from rpciod and nfsiod workers.
Set nfsiod's affinity scope to WQ_AFFN_SMT so each SMT group gets
its own pool and queue_work_on() contends only with sibling
threads. Enqueue contention disappears and dequeue contention drops
to 1.4%. Throughput is unchanged because the workload is
transport-limited, but the freed cycles cut submission latency
variance by 67% (slat stdev 31.6 us to 10.5 us), IOPS stdev by
31%, and p99.9 completion latency by 11%.
Suggested-by: Tejun Heo <tj@xxxxxxxxxx>
Signed-off-by: Chuck Lever <cel@xxxxxxxxxx>
---
fs/nfs/inode.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c
index 107a2135029d..21c4560696bd 100644
--- a/fs/nfs/inode.c
+++ b/fs/nfs/inode.c
@@ -2618,11 +2618,16 @@ static void nfsiod_stop(void)
*/
static int nfsiod_start(void)
{
+ int err;
+
dprintk("RPC: creating workqueue nfsiod\n");
nfsiod_workqueue = alloc_workqueue("nfsiod",
WQ_MEM_RECLAIM | WQ_UNBOUND | WQ_SYSFS, 0);
if (nfsiod_workqueue == NULL)
return -ENOMEM;
+ err = workqueue_set_affn_scope(nfsiod_workqueue, WQ_AFFN_SMT);
+ if (err)
+ pr_warn("nfsiod: failed to set SMT affinity scope: %d\n", err);
#if IS_ENABLED(CONFIG_NFS_LOCALIO)
/*
* localio writes need to use a normal (non-memreclaim) workqueue.
--
2.55.0