3 cacheline io_kiocb
cover 3 cacheline io_kiocb
That's not final for a several reasons, but good enough for discussion. That brings io_kiocb down to 192B. I didn't try to benchmark it properly, but quick nop test gave +5% throughput increase. 7531 vs 7910 KIOPS with fio/t/io_uring
The whole situation is obviously a bunch of tradeoffs. For instance, instead of shrinking it, we can inline apoll to speed apoll path.
[2/2] just for a reference, I'm thinking about other ways to shrink it. e.g. ->link_list can be a single-linked list with linked tiemouts storing a back-reference. This can turn out to be better, because that would move ->fixed_file_refs to the 2nd cacheline, so we won't ever touch 3rd cacheline in the submission path.
Any other ideas?
note: on top of for-5.9/io_uring,f56040b819998 ("io_uring: deduplicate io_grab_files() calls")
Pavel Begunkov (2):
io_uring: allocate req->work dynamically
io_uring: unionise ->apoll and ->work
fs/io-wq.h | 1 + fs/io_uring.c | 207 ++++++++++++++++++++++++++------------------------ 2 files changed, 110 insertions(+), 98 deletions(-)
Signature · 1 line
2.24.0
PATCH 1/2 io_uring: allocate req->work dynamically
req->work takes a lot of space and is not needed in hot path, don't embed it into struct io_kiocb and allocate dynamically. The changes are pretty straightforward, the only noticible thing is a ->private field in io_wq_work growing it to 64B, and used to store a reference to the request.
That shrinks io_kiocb to 200 bytes
--- fs/io-wq.h | 1 + fs/io_uring.c | 203 +++++++++++++++++++++++++------------------------- 2 files changed, 104 insertions(+), 100 deletions(-)
fs/io-wq.h +1 -0
@@ -85,6 +85,7 @@ static inline void wq_list_del(struct io_wq_work_list *list,struct io_wq_work {struct io_wq_work_node list; +void *private;struct files_struct *files;struct mm_struct *mm;const struct cred *creds;
fs/io_uring.c +103 -100
435 lines not on this page · raw message
Signature · 1 line
2.24.0
PATCH 2/2 io_uring: unionise ->apoll and ->work
In reply to Pavel BegunkovSave a bit of space by placing ->apoll and ->work ptrs into a union, making io_kiocb to take 192B (3 cachelines)
note: this patch is just for reference, there are other probably better ways to save 8B.
--- fs/io_uring.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-)
fs/io_uring.c +11 -3
@@ -597,6 +597,7 @@ enum {struct async_poll {struct io_poll_iocb poll;struct io_poll_iocb *double_poll; +struct io_wq_work *work; };/*
@@ -658,8 +659,10 @@ struct io_kiocb {* async armed poll handlers for regular commands. */ struct hlist_node hash_node; -struct async_poll *apoll; -struct io_wq_work *work; +union { +struct async_poll *apoll; +struct io_wq_work *work; + };struct callback_head task_work; };
@@ -4676,6 +4679,8 @@ static void io_async_task_func(struct callback_head *cb)io_poll_remove_double (req, apoll->double_poll);spin_unlock_irq (&ctx->completion_lock); + req->work = apoll->work; +if (!READ_ONCE (apoll->poll.canceled))__io_req_task_submit (req);else
@@ -4765,6 +4770,7 @@ static bool io_arm_poll_handler(struct io_kiocb *req)if (unlikely (!apoll))return false; apoll->double_poll =NULL ; + apoll->work = req->work; req->flags |=REQ_F_POLLED ;io_get_req_task (req);
@@ -4785,6 +4791,7 @@ static bool io_arm_poll_handler(struct io_kiocb *req)if (ret) {io_poll_remove_double (req, apoll->double_poll);spin_unlock_irq (&ctx->completion_lock); + req->work = apoll->work;kfree (apoll->double_poll);kfree (apoll);return false;
@@ -4826,6 +4833,7 @@ static bool io_poll_remove_one(struct io_kiocb *req)/* non-poll requests have submit ref still */ do_complete =__io_poll_remove_one (req, &apoll->poll);if (do_complete) { + req->work = apoll->work;io_put_req (req);kfree (apoll->double_poll);kfree (apoll);
@@ -4962,7 +4970,7 @@ static int io_poll_add(struct io_kiocb *req)/* ->work is in union with hash_node and others */ io_req_clean_work (req); - req->flags &= ~REQ_F_WORK_INITIALIZED ; + req->work =NULL ;INIT_HLIST_NODE (&req->hash_node); ipt.pt._qproc = io_poll_queue_proc;
Signature · 1 line
2.24.0
Jens Axboe
maintainerQuoted 8 lines · Pavel Begunkov
That's not final for a several reasons, but good enough for discussion. That brings
io_kiocbdown to 192B. I didn't try to benchmark it properly, but quick nop test gave +5% throughput increase. 7531 vs 7910 KIOPS with fio/t/io_uringThe whole situation is obviously a bunch of tradeoffs. For instance, instead of shrinking it, we can inline apoll to speed apoll path.
Quoted 6 lines
[2/2] just for a reference, I'm thinking about other ways to shrink it. e.g. ->link_list can be a single-linked list with linked tiemouts storing a back-reference. This can turn out to be better, because that would move ->fixed_file_refs to the 2nd cacheline, so we won't ever touch 3rd cacheline in the submission path.
Any other ideas?
Nothing noticeable for me, still about the same performance. But generally speaking, I don't necessarily think we need to go all in on making this as tiny as possible. It's much more important to chase the items where we only use 2 cachelines for the hot path, and then we have the extra space in there already for the semi hot paths like poll driven retry. Yes, we're still allocating from a pool that has slightly larger objects, but that doesn't really matter _that_ much. Avoiding an extra kmalloc+kfree for the semi hot paths are a bigger deal than making io_kiocb smaller and smaller.
That said, for no-brainer changes, we absolutely should make it smaller. I just don't want to jump through convoluted hoops to get there.
Signature · 1 line
Jens Axboe
Quoted 26 lines · Jens Axboe
Quoted 8 lines · Pavel Begunkov
That's not final for a several reasons, but good enough for discussion. That brings
io_kiocbdown to 192B. I didn't try to benchmark it properly, but quick nop test gave +5% throughput increase. 7531 vs 7910 KIOPS with fio/t/io_uringThe whole situation is obviously a bunch of tradeoffs. For instance, instead of shrinking it, we can inline apoll to speed apoll path.
Quoted 6 lines
[2/2] just for a reference, I'm thinking about other ways to shrink it. e.g. ->link_list can be a single-linked list with linked tiemouts storing a back-reference. This can turn out to be better, because that would move ->fixed_file_refs to the 2nd cacheline, so we won't ever touch 3rd cacheline in the submission path.
Any other ideas?Nothing noticeable for me, still about the same performance. But generally speaking, I don't necessarily think we need to go all in on making this as tiny as possible. It's much more important to chase the items where we only use 2 cachelines for the hot path, and then we have the extra space in there already for the semi hot paths like poll driven retry. Yes, we're still allocating from a pool that has slightly larger objects, but that doesn't really matter _that_ much. Avoiding an extra kmalloc+kfree for the semi hot paths are a bigger deal than making
io_kiocbsmaller and smaller.
Quoted 2 lines
That said, for no-brainer changes, we absolutely should make it smaller. I just don't want to jump through convoluted hoops to get there.
Agree, but that's not the end goal. The first point is to kill the union, but it already has enough space for that.
The second is to see, whether we can use the space in a better way. From the high level perspective ->apoll and ->work are alike and both serve to provide asynchronous paths, hence the idea to swap them naturally comes to mind. TBH, I don't think it'd do much, because init of ->io would probably hide any benefit.
Signature · 1 line
Pavel Begunkov
Jens Axboe
maintainerQuoted 27 lines · Pavel Begunkov
Quoted 26 lines · Jens Axboe
Quoted 8 lines · Pavel Begunkov
That's not final for a several reasons, but good enough for discussion. That brings
io_kiocbdown to 192B. I didn't try to benchmark it properly, but quick nop test gave +5% throughput increase. 7531 vs 7910 KIOPS with fio/t/io_uringThe whole situation is obviously a bunch of tradeoffs. For instance, instead of shrinking it, we can inline apoll to speed apoll path.
Quoted 6 lines
[2/2] just for a reference, I'm thinking about other ways to shrink it. e.g. ->link_list can be a single-linked list with linked tiemouts storing a back-reference. This can turn out to be better, because that would move ->fixed_file_refs to the 2nd cacheline, so we won't ever touch 3rd cacheline in the submission path.
Any other ideas?Nothing noticeable for me, still about the same performance. But generally speaking, I don't necessarily think we need to go all in on making this as tiny as possible. It's much more important to chase the items where we only use 2 cachelines for the hot path, and then we have the extra space in there already for the semi hot paths like poll driven retry. Yes, we're still allocating from a pool that has slightly larger objects, but that doesn't really matter _that_ much. Avoiding an extra kmalloc+kfree for the semi hot paths are a bigger deal than making
io_kiocbsmaller and smaller.
Quoted 5 lines
Quoted 2 lines
That said, for no-brainer changes, we absolutely should make it smaller. I just don't want to jump through convoluted hoops to get there.
Agree, but that's not the end goal. The first point is to kill the union, but it already has enough space for that.
Right
Quoted 4 lines
The second is to see, whether we can use the space in a better way. From the high level perspective ->apoll and ->work are alike and both serve to provide asynchronous paths, hence the idea to swap them naturally comes to mind.
Totally agree, which is why the union of those kind of makes sense. We're definitely NOT using them at the same time, but the fact that we had various mm/creds/whatnot in the work_struct made that a bit iffy.
Quoted 2 lines
TBH, I don't think it'd do much, because init of ->io would probably hide any benefit.
There should be no ->io init/alloc for this test case.
Signature · 1 line
Jens Axboe
Quoted 36 lines · Jens Axboe
Quoted 27 lines · Pavel Begunkov
Quoted 26 lines · Jens Axboe
Quoted 8 lines · Pavel Begunkov
That's not final for a several reasons, but good enough for discussion. That brings
io_kiocbdown to 192B. I didn't try to benchmark it properly, but quick nop test gave +5% throughput increase. 7531 vs 7910 KIOPS with fio/t/io_uringThe whole situation is obviously a bunch of tradeoffs. For instance, instead of shrinking it, we can inline apoll to speed apoll path.
Quoted 6 lines
[2/2] just for a reference, I'm thinking about other ways to shrink it. e.g. ->link_list can be a single-linked list with linked tiemouts storing a back-reference. This can turn out to be better, because that would move ->fixed_file_refs to the 2nd cacheline, so we won't ever touch 3rd cacheline in the submission path.
Any other ideas?Nothing noticeable for me, still about the same performance. But generally speaking, I don't necessarily think we need to go all in on making this as tiny as possible. It's much more important to chase the items where we only use 2 cachelines for the hot path, and then we have the extra space in there already for the semi hot paths like poll driven retry. Yes, we're still allocating from a pool that has slightly larger objects, but that doesn't really matter _that_ much. Avoiding an extra kmalloc+kfree for the semi hot paths are a bigger deal than making
io_kiocbsmaller and smaller.Quoted 5 lines
Quoted 2 lines
That said, for no-brainer changes, we absolutely should make it smaller. I just don't want to jump through convoluted hoops to get there.
Agree, but that's not the end goal. The first point is to kill the union, but it already has enough space for that.
Right
Quoted 8 lines
Quoted 4 lines
The second is to see, whether we can use the space in a better way. From the high level perspective ->apoll and ->work are alike and both serve to provide asynchronous paths, hence the idea to swap them naturally comes to mind.
Totally agree, which is why the union of those kind of makes sense. We're definitely NOT using them at the same time, but the fact that we had various
mm/creds/whatnotin thework_structmade that a bit iffy.
Thinking of it, if combined with work de-init as you proposed before, it's probably possible to make a layout similar to the one below
struct io_kiocb {
...
struct hlist_node hash_node;
struct callback_head task_work;
union {
struct io_wq_work work;
struct async_poll apoll;
};
};Saves ->apoll kmalloc(), and the actual work de-init would be negligibly rare. Worth to try
Quoted 5 lines
Quoted 3 lines
TBH, I don't think it'd do much, because init of ->io would probably hide any benefit.
There should be no ->io init/alloc for this test case.
I mean, before getting into io_arm_poll_handler(), it should get -EAGAIN in io_{read,write}() and initialise ->io in io_setup_async_rw(), at least for READV, WRITEV.
Signature · 1 line
Pavel Begunkov
Jens Axboe
maintainerQuoted 59 lines · Pavel Begunkov
Quoted 36 lines · Jens Axboe
Quoted 27 lines · Pavel Begunkov
Quoted 26 lines · Jens Axboe
Quoted 8 lines · Pavel Begunkov
That's not final for a several reasons, but good enough for discussion. That brings
io_kiocbdown to 192B. I didn't try to benchmark it properly, but quick nop test gave +5% throughput increase. 7531 vs 7910 KIOPS with fio/t/io_uringThe whole situation is obviously a bunch of tradeoffs. For instance, instead of shrinking it, we can inline apoll to speed apoll path.
Quoted 6 lines
[2/2] just for a reference, I'm thinking about other ways to shrink it. e.g. ->link_list can be a single-linked list with linked tiemouts storing a back-reference. This can turn out to be better, because that would move ->fixed_file_refs to the 2nd cacheline, so we won't ever touch 3rd cacheline in the submission path.
Any other ideas?Nothing noticeable for me, still about the same performance. But generally speaking, I don't necessarily think we need to go all in on making this as tiny as possible. It's much more important to chase the items where we only use 2 cachelines for the hot path, and then we have the extra space in there already for the semi hot paths like poll driven retry. Yes, we're still allocating from a pool that has slightly larger objects, but that doesn't really matter _that_ much. Avoiding an extra kmalloc+kfree for the semi hot paths are a bigger deal than making
io_kiocbsmaller and smaller.Quoted 5 lines
Quoted 2 lines
That said, for no-brainer changes, we absolutely should make it smaller. I just don't want to jump through convoluted hoops to get there.
Agree, but that's not the end goal. The first point is to kill the union, but it already has enough space for that.
Right
Quoted 8 lines
Quoted 4 lines
The second is to see, whether we can use the space in a better way. From the high level perspective ->apoll and ->work are alike and both serve to provide asynchronous paths, hence the idea to swap them naturally comes to mind.
Totally agree, which is why the union of those kind of makes sense. We're definitely NOT using them at the same time, but the fact that we had various
mm/creds/whatnotin thework_structmade that a bit iffy.Thinking of it, if combined with work de-init as you proposed before, it's probably possible to make a layout similar to the one below
struct io_kiocb { ... struct hlist_node hash_node; struct callback_head task_work; union { struct io_wq_work work; struct async_poll apoll; }; };
Quoted 2 lines
Saves ->apoll
kmalloc(), and the actual work de-init would be negligibly rare. Worth to try
And hopefully get rid of the stupid apoll->work and the copy back and forth... But yes, this would be most excellent, and an ideal layout.
Quoted 8 lines
Quoted 4 lines
Quoted 2 lines
TBH, I don't think it'd do much, because init of ->io would probably hide any benefit.
There should be no ->io init/alloc for this test case.
I mean, before getting into
io_arm_poll_handler(), it should get-EAGAINin io_{read,write}() and initialise ->io inio_setup_async_rw(), at least for READV, WRITEV.
Sure, but for my testing, there's never an EAGAIN, so I won't be exercising that path for the peak testing.
Signature · 1 line
Jens Axboe