On 2022/7/28 18:51, Ming Lei wrote: > On Thu, Jul 28, 2022 at 05:31:22PM +0800, ZiyangZhang wrote: >> 1. Introduction: >> UBLK_IO_NEED_GET_DATA is a new ublk IO command. It is designed for a user >> application who wants to allocate IO buffer and set IO buffer address >> only after it receives an IO request from ublksrv. This is a reasonable >> scenario because these users may use a RPC framework as one IO backend >> to handle IO requests passed from ublksrv. And a RPC framework may >> allocate its own buffer(or memory pool). >> >> This new feature (UBLK_F_NEED_GET_DATA) is optional for ublk users. >> Related userspace code has been added in ublksrv[1] as one pull request. >> >> We have add some test cases in ublksrv and all of them pass. The >> performance result shows that this new feature does bring additional >> latency because one IO is issued back to ublk_drv once again to copy data >> from bio vectors to user-provided data buffer. >> >> 2. Background: >> For now, ublk requires the user to set IO buffer address in advance(with >> last UBLK_IO_COMMIT_AND_FETCH_REQ command)so the user has to >> pre-allocate IO buffer. >> >> For READ requests, this work flow looks good because the data copy >> happens after user application gets a cqe and the kernel copies data. >> So user application can allocate IO buffer, copy data to be read into >> it, and issues a sqe with the newly allocated IO buffer. >> >> However, for WRITE requests, this work flow looks weird because >> the data copy happens in a task_work before the user application gets one >> cqe. So it is inconvenient for users who allocates(or fetch from a >> memory pool)buffer after it gets one request(and know the actual data >> size). For these users, they have to memcpy from ublksrv's pre-allocated >> buffer to their internal buffer(such as RPC buffer). We think this >> additional memcpy could be a bottleneck and it is avoidable. >> >> 2. Design: >> Consider add a new feature flag: UBLK_F_NEED_GET_DATA. >> >> If user sets this new flag(through libublksrv) and pass it to kernel >> driver, ublk kernel driver should returns a cqe with >> UBLK_IO_RES_NEED_GET_DATA after a new blk-mq WRITE request comes. >> >> A user application now can allocate data buffer for writing and pass its >> address in UBLK_IO_NEED_GET_DATA command after ublk kernel driver returns >> cqe with UBLK_IO_RES_NEED_GET_DATA. >> >> After the kernel side gets the sqe (UBLK_IO_NEED_GET_DATA command), it >> now copies(address pinned, indeed) data to be written from bio vectors >> to newly returned IO data buffer. >> >> Finally, the kernel side returns UBLK_IO_RES_OK and ublksrv handles the >> IO request as usual.The new feature: UBLK_F_NEED_GET_DATA is enabled on >> demand ublksrv still can pre-allocate data buffers with task_work. >> >> 3. Evaluation: >> Related userspace code and tests have been added in ublksrv[1] as one >> pull request. We evaluate performance based on this PR. >> >> We have tested write latency with: >> (1) No UBLK_F_NEED_GET_DATA(the old commit) as baseline >> (2) UBLK_F_NEED_GET_DATA enabled/disabled >> on demo_null and demo_event of newest ublksrv project. >> >> Config of fio:bs=4k, iodepth=1, numjobs=1, rw=write/randwrite, direct=1, >> ioengine=libaio. >> >> Here is the comparison of lat(usec) in fio: >> >> demo_null: >> write: 28.74(baseline) -- 28.77(disable) -- 57.20(enable) >> randwrite: 27.81(baseline) -- 28.51(disable) -- 54.81(enable) >> >> demo_event: >> write: 46.45(baseline) -- 43.31(disable) -- 75.50(enable) >> randwrite: 45.39(baseline) -- 43.66(disable) -- 76.02(enable) > > The data is interesting, and I guess the enable latency data could become > much less if 64 iodepth & 16 batch is used > --iodepth=64 --iodepth_batch_submit=16 --iodepth_batch_complete_min=16 Yes, multiple UBLK_IO_NEED_GET_DATA cmds can share the same io_uring_enter() syscall with bigger iodepth & batch. > >> >> Looks like: >> (1) UBLK_F_NEED_GET_DATA does not introduce additional overhead when >> comparing baseline and disable. >> (2) enabling UBLK_F_NEED_GET_DATA adds about two times more latency >> than disabling it. And it is reasonable since the IO goes through >> the total ublk IO stack(ubd_drv <--> ublksrv) once again. >> (3) demo_null and demo_event are both null targets. And I think this >> overhead is not too heavy if real data handling backend is used. >> >> Without UBLK_IO_NEED_GET_DATA, an additional memcpy(from pre-allocated >> ublksrv buffer to user's buffer) is necessary for a WRITE request. >> However, UBLK_IO_NEED_GET_DATA does bring addtional syscall >> (io_uring_enter). To prove the value of UBLK_IO_NEED_GET_DATA, we test >> the single IO latency (usec) of demo_null with: >> (1) UBLK_F_NEED_GET_DATA disabled; additional memcpy >> (2) UBLK_F_NEED_GET_DATA enabled >> >> Config of fio:iodepth=1, numjobs=1, rw=randwrite, direct=1, >> ioengine=libaio. >> >> For block size, we choose 4k/64k/128k/256k/512k/1m. Note that with 1m block >> size, the original IO request will be split into two blk-mq requests. >> >> Here is the comparison of lat(usec) in fio: >> >> 2 memcpy, w/o NEED_GET_DATA 1 memcpy, w/ NEED_GET_DATA >> 4k-randwrite: 9.65 10.06 >> 64k-randwrite: 15.19 13.38 >> 128k-randwrite: 19.47 17.77 >> 256k-randwrite: 32.63 25.33 >> 512k-randwrite: 90.57 46.08 >> 1m-randwrite: 177.06 117.26 >> >> We find that with bigger block size, cases with one memcpy w/ NEED_GET_DATA >> result in lower latency than cases with two memcpy w/o NEED_GET_DATA. >> Therefore, we think NEED_GET_DATA is suitable for bigger block size, >> such as 512B or 1MB. > > With 64 iodepth and submit/completion batching, I think NEED_GET_DATA > could become less. > > Anyway, it is very helpful to share the test data, nice job! Thanks. I have also noticed that with 128 iodepth, 16 batch(in test cases of ublksrv), NEED_GET_DATA behaves well. Thanks, Zhang.