io_uring/uring_cmd: defer SQE copying until it's needed
authorJens Axboe <axboe@kernel.dk>
Wed, 20 Mar 2024 21:23:47 +0000 (15:23 -0600)
committerJens Axboe <axboe@kernel.dk>
Mon, 15 Apr 2024 14:10:25 +0000 (08:10 -0600)
The previous commit turned on async data for uring_cmd, and did the
basic conversion of setting everything up on the prep side. However, for
a lot of use cases, -EIOCBQUEUED will get returned on issue, as the
operation got successfully queued. For that case, a persistent SQE isn't
needed, as it's just used for issue.

Unless execution goes async immediately, defer copying the double SQE
until it's necessary.

This greatly reduces the overhead of such commands, as evidenced by
a perf diff from before and after this change:

    10.60%     -8.58%  [kernel.vmlinux]  [k] io_uring_cmd_prep

where the prep side drops from 10.60% to ~2%, which is more expected.
Performance also rises from ~113M IOPS to ~122M IOPS, bringing us back
to where it was before the async command prep.

Tested-by: Anuj Gupta <anuj20.g@samsung.com>
Reviewed-by: Anuj Gupta <anuj20.g@samsung.com>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
io_uring/uring_cmd.c

index 9bd0ba87553ff059a9273527eed356f5f1ce072f..92346b5d9f5b088b34363d3d5707107734fd902f 100644 (file)
@@ -182,12 +182,18 @@ static int io_uring_cmd_prep_setup(struct io_kiocb *req,
        struct uring_cache *cache;
 
        cache = io_uring_async_get(req);
-       if (cache) {
-               memcpy(cache->sqes, sqe, uring_sqe_size(req->ctx));
-               ioucmd->sqe = req->async_data;
+       if (unlikely(!cache))
+               return -ENOMEM;
+
+       if (!(req->flags & REQ_F_FORCE_ASYNC)) {
+               /* defer memcpy until we need it */
+               ioucmd->sqe = sqe;
                return 0;
        }
-       return -ENOMEM;
+
+       memcpy(req->async_data, sqe, uring_sqe_size(req->ctx));
+       ioucmd->sqe = req->async_data;
+       return 0;
 }
 
 int io_uring_cmd_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe)
@@ -245,8 +251,15 @@ int io_uring_cmd(struct io_kiocb *req, unsigned int issue_flags)
        }
 
        ret = file->f_op->uring_cmd(ioucmd, issue_flags);
-       if (ret == -EAGAIN || ret == -EIOCBQUEUED)
-               return ret;
+       if (ret == -EAGAIN) {
+               struct uring_cache *cache = req->async_data;
+
+               if (ioucmd->sqe != (void *) cache)
+                       memcpy(cache, ioucmd->sqe, uring_sqe_size(req->ctx));
+               return -EAGAIN;
+       } else if (ret == -EIOCBQUEUED) {
+               return -EIOCBQUEUED;
+       }
 
        if (ret < 0)
                req_set_fail(req);