Re: [RFC PATCH] fuse: give wakeup hints to the scheduler for synchronous requests
From: Xuewen Yan
Date: Thu Aug 06 2026 - 03:44:42 EST
Hi Miklos,
Any comments?
Thanks!
On Fri, Jul 31, 2026 at 3:03 PM Xuewen Yan <xuewen.yan@xxxxxxxxxx> wrote:
>
> When a synchronous FUSE request is sent, the in-kernel client queues it
> on fiq->pending and wakes the userspace daemon sleeping in
> fuse_dev_do_read()->wait_event_interruptible_exclusive(fiq->waitq, ...).
> The client then blocks in request_wait_answer() waiting for the reply,
> so the waker is about to go to sleep: this is exactly the pattern that
> WF_SYNC is meant to optimise.
> As Peter Zijlstra explained in the earlier discussion [1],
> WF_SYNC is a hint that the waker is about to sleep and the waker and
> wakee share data, so stacking the woken thread on the current CPU
> is beneficial for cache locality instead of searching for an idle one.
>
> Add a wake_up_sync() wrapper for task on the synchronous request path.
>
> Performance:
>
> On an Android big.LITTLE device where the FUSE daemon (MediaProvider)
> runs as a background service on the little cores while foreground
> applications run on the big cores, the synchronous wakeup hint lets
> the scheduler pull the daemon thread onto the big core that is
> issuing the request, where the request data is cache-hot. Measured
> by qixiaoyu [2] on a 2000-picture zip decompression to /sdcard:
>
> ------------------------------------------
> | Default | patched | Improvement |
> ------------------------------------------
> | 13.0 s | 7.0 s | 46% |
> ------------------------------------------
>
> Server thread wall duration: 3583 ms -> 1276 ms
> Server runs on big core: 5% -> 79%
>
> The original 4K-file copy/compress/decompress workload [1] on the
> same kind of device showed a ~28% improvement (13.8s -> 9.9s).
>
> Note: Miklos reported [2] that on his test box he could not observe
> an actual migration from wake_up_interruptible_sync(); the benefit
> appears to be most visible on asymmetric topologies (big.LITTLE,
> where the daemon normally lives on a little core) and on workloads
> dominated by small synchronous requests. No regression was
> reported on the symmetric- SMP test setups tried.
>
> The earlier version of this change [1] added a `bool sync` argument
> to all three hooks of `struct fuse_iqueue_ops` and threaded it
> through virtio_fs as well. Miklos questioned the interface churn,
> and the patch has been stalled since.
>
> Re-work it so the exported interface is left alone. The hint is
> carried in a new FR_SYNC_WAKEUP bit of the existing `fuse_req->flags`
> bitfield (an `unsigned long`, so no layout change):
>
> - __fuse_request_send() sets the flag before fuse_send_one().
> - fuse_dev_queue_req() consumes it with test_and_clear_bit() and
> forwards the result to fuse_dev_wake_and_unlock(), which then
> picks wake_up_sync() or wake_up().
> - The forget, interrupt and resend paths pass `false` explicitly,
> preserving their original wake_up() behaviour.
>
> Only /dev/fuse ever wakes fiq->waitq; virtio_fs and fuse_uring
> dispatch through their own transport and never call wake_up(), so
> threading `sync` through their ops would just add an unused argument.
> test_and_clear_bit() makes the flag a one-shot hint that cannot leak
> into a future requeue, and no extra cleanup is needed in
> fuse_request_end()/fuse_put_request().
>
> [1] https://lore.kernel.org/lkml/1638780405-38026-1-git-send-email-quic_pragalla@xxxxxxxxxxx/
> [2] https://lore.kernel.org/lkml/20221222093407.GA1141@mi-HP-ProDesk-680-G4-MT/
>
> This work is based on "Pradeep P V K <quic_pragalla@xxxxxxxxxxx>" and
> "Pavankumar Kondeti <quic_pkondeti@xxxxxxxxxxx>"
>
> Assisted-by: TRAE:GLM-5.2
> Signed-off-by: Xuewen Yan <xuewen.yan@xxxxxxxxxx>
> ---
> fs/fuse/dev.c | 22 ++++++++++++++++------
> fs/fuse/fuse_dev_i.h | 3 +++
> 2 files changed, 19 insertions(+), 6 deletions(-)
>
> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
> index 5763a7cd3b37..689470a548a2 100644
> --- a/fs/fuse/dev.c
> +++ b/fs/fuse/dev.c
> @@ -210,10 +210,13 @@ EXPORT_SYMBOL_GPL(fuse_req_hash);
> /*
> * A new request is available, wake fiq->waitq
> */
> -static void fuse_dev_wake_and_unlock(struct fuse_iqueue *fiq)
> +static void fuse_dev_wake_and_unlock(struct fuse_iqueue *fiq, bool sync)
> __releases(fiq->lock)
> {
> - wake_up(&fiq->waitq);
> + if (sync)
> + wake_up_sync(&fiq->waitq);
> + else
> + wake_up(&fiq->waitq);
> kill_fasync(&fiq->fasync, SIGIO, POLL_IN);
> spin_unlock(&fiq->lock);
> }
> @@ -230,7 +233,7 @@ void fuse_dev_queue_forget(struct fuse_iqueue *fiq,
> if (fiq->connected) {
> fiq->forget_list_tail->next = forget;
> fiq->forget_list_tail = forget;
> - fuse_dev_wake_and_unlock(fiq);
> + fuse_dev_wake_and_unlock(fiq, false);
> } else {
> kfree(forget);
> spin_unlock(&fiq->lock);
> @@ -251,7 +254,7 @@ void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req)
> list_del_init(&req->intr_entry);
> spin_unlock(&fiq->lock);
> } else {
> - fuse_dev_wake_and_unlock(fiq);
> + fuse_dev_wake_and_unlock(fiq, false);
> }
> } else {
> spin_unlock(&fiq->lock);
> @@ -281,11 +284,13 @@ EXPORT_SYMBOL_GPL(fuse_request_assign_unique);
>
> static void fuse_dev_queue_req(struct fuse_iqueue *fiq, struct fuse_req *req)
> {
> + bool sync = test_and_clear_bit(FR_SYNC_WAKEUP, &req->flags);
> +
> spin_lock(&fiq->lock);
> if (fiq->connected) {
> fuse_request_assign_unique_locked(fiq, req);
> list_add_tail(&req->list, &fiq->pending);
> - fuse_dev_wake_and_unlock(fiq);
> + fuse_dev_wake_and_unlock(fiq, sync);
> } else {
> spin_unlock(&fiq->lock);
> req->out.h.error = -ENOTCONN;
> @@ -752,6 +757,11 @@ static void __fuse_request_send(struct fuse_req *req)
> /* acquire extra reference, since request is still needed after
> fuse_request_end() */
> __fuse_get_request(req);
> + /*
> + * This is a synchronous request: the caller will block waiting for
> + * the answer. Hint the scheduler via wake_up_sync().
> + */
> + set_bit(FR_SYNC_WAKEUP, &req->flags);
> fuse_send_one(fiq, req);
>
> request_wait_answer(req);
> @@ -1806,7 +1816,7 @@ void fuse_chan_resend(struct fuse_chan *fch)
> }
> /* iq and pq requests are both oldest to newest */
> list_splice(&to_queue, &fiq->pending);
> - fuse_dev_wake_and_unlock(fiq);
> + fuse_dev_wake_and_unlock(fiq, false);
> }
>
> /* Look up request on processing list by unique ID */
> diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h
> index 668c8391d61c..40a791094813 100644
> --- a/fs/fuse/fuse_dev_i.h
> +++ b/fs/fuse/fuse_dev_i.h
> @@ -38,6 +38,8 @@ struct fuse_iqueue;
> * @FR_PRIVATE: request is on private list
> * @FR_ASYNC: request is asynchronous
> * @FR_URING: request is handled through fuse-io-uring
> + * @FR_SYNC_WAKEUP: use synchronous wakeup when queueing this request to
> + * give the scheduler a hint about the waker task
> */
> enum fuse_req_flag {
> FR_ISREPLY,
> @@ -53,6 +55,7 @@ enum fuse_req_flag {
> FR_PRIVATE,
> FR_ASYNC,
> FR_URING,
> + FR_SYNC_WAKEUP,
> };
>
> /**
> --
> 2.25.1
>
>