On 11.09.19 г. 19:15 ч., Jens Axboe wrote: > We currently merge async work items if we see a strict sequential hit. > This helps avoid unnecessary workqueue switches when we don't need > them. We can extend this merging to cover cases where it's not a strict > sequential hit, but the IO still fits within the same page. If an > application is doing multiple requests within the same page, we don't > want separate workers waiting on the same page to complete IO. It's much > faster to let the first worker bring in the page, then operate on that > page from the same worker to complete the next request(s). > > Signed-off-by: Jens Axboe <axboe@xxxxxxxxx> > > --- > > diff --git a/fs/io_uring.c b/fs/io_uring.c > index 03fcd974fd1d..4bc3ee4ea81f 100644 > --- a/fs/io_uring.c > +++ b/fs/io_uring.c > @@ -167,7 +167,7 @@ struct async_list { > struct list_head list; > > struct file *file; > - off_t io_end; > + off_t io_start; > size_t io_len; > }; > > @@ -1189,6 +1189,28 @@ static ssize_t io_import_iovec(struct io_ring_ctx *ctx, int rw, > return import_iovec(rw, buf, sqe_len, UIO_FASTIOV, iovec, iter); > } > > +static inline bool io_should_merge(struct async_list *al, struct kiocb *kiocb) > +{ > + if (al->file == kiocb->ki_filp) { > + off_t start, end; > + > + /* > + * Allow merging if we're anywhere in the range of the same > + * page. Generally this happens for sub-page reads or writes, > + * and it's beneficial to allow the first worker to bring the > + * page in and the piggy backed work can then work on the > + * cached page. > + */ > + start = al->io_start & PAGE_MASK; nit: round_down(al->io_start, PAGE_SIZE); > + end = (al->io_start + al->io_len + PAGE_SIZE - 1) & PAGE_MASK; nit: round_up(al->io_start+io_len, PAGE_SIZE) > + if (kiocb->ki_pos >= start && kiocb->ki_pos <= end) > + return true; > + } > + > + al->file = NULL; > + return false; > +} > + > /* > * Make a note of the last file/offset/direction we punted to async > * context. We'll use this information to see if we can piggy back a > @@ -1200,9 +1222,8 @@ static void io_async_list_note(int rw, struct io_kiocb *req, size_t len) > struct async_list *async_list = &req->ctx->pending_async[rw]; > struct kiocb *kiocb = &req->rw; > struct file *filp = kiocb->ki_filp; > - off_t io_end = kiocb->ki_pos + len; > > - if (filp == async_list->file && kiocb->ki_pos == async_list->io_end) { > + if (io_should_merge(async_list, kiocb)) { > unsigned long max_bytes; > > /* Use 8x RA size as a decent limiter for both reads/writes */ > @@ -1215,17 +1236,16 @@ static void io_async_list_note(int rw, struct io_kiocb *req, size_t len) > req->flags |= REQ_F_SEQ_PREV; > async_list->io_len += len; > } else { > - io_end = 0; > - async_list->io_len = 0; > + async_list->file = NULL; > } > } > > /* New file? Reset state. */ > if (async_list->file != filp) { > - async_list->io_len = 0; > + async_list->io_start = kiocb->ki_pos; > + async_list->io_len = len; > async_list->file = filp; > } > - async_list->io_end = io_end; > } > > static int io_read(struct io_kiocb *req, const struct sqe_submit *s, > @@ -1994,7 +2014,7 @@ static void io_sq_wq_submit_work(struct work_struct *work) > */ > static bool io_add_to_prev_work(struct async_list *list, struct io_kiocb *req) > { > - bool ret = false; > + bool ret; > > if (!list) > return false; >