Re: [RFC PATCH] fuse: give wakeup hints to the scheduler for synchronous requests
Xuewen Yan <[email protected]>
| Newsgroups | dev.linux.lists.fuse-devel,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <CAB8ipk8CyuPRQv2mSwnc-ssj2Aes=eFdpDDk9nuxEwxMOCS9Dg@mail.gmail.com> |
Hi Miklos, Any comments? Thanks! On Fri, Jul 31, 2026 at 3:03 PM Xuewen Yan <[email protected]> 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/[email protected]/ > [2] https://lore.kernel.org/lkml/20221222093407.GA1141@mi-HP-ProDesk-680-G4-MT/ > > This work is based on "Pradeep P V K <[email protected]>" and > "Pavankumar Kondeti <[email protected]>" > > Assisted-by: TRAE:GLM-5.2 > Signed-off-by: Xuewen Yan <[email protected]> > --- > 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 > >