Spdk/test/unit/lib/sock/uring.c/uring_ut.c
Richael Zhuang 9bff828f99 sock: introduce dynamic zerocopy according to data size
MSG_ZEROCOPY is not always effective as mentioned in
https://www.kernel.org/doc/html/v4.15/networking/msg_zerocopy.html.

Currently in spdk, once we enable sendmsg zerocopy, then all data
transferred through _sock_flush are sent with zerocopy, and vice
versa. Here dynamic zerocopy is introduced to allow data sent with
MSG_ZEROCOPY or not according to its size, which can be enabled by
setting "enable_dynamic_zerocopy" as true.

Test with 16 P4610 NVMe SSD, 2 initiators, target's and initiators'
configurations are the same as spdk report:
https://ci.spdk.io/download/performance-reports/SPDK_tcp_perf_report_2104.pdf

For posix socket, rw_percent=0(randwrite), it has 1.9%~8.3% performance boost
tested with target 1~40 cpu cores and qdepth=128,256,512. And it has no obvious
influence when read percentage is greater than 50%.

For uring socket, rw_percent=0(randwrite), it has 1.8%~7.9% performance boost
tested with target 1~40 cpu cores and qdepth=128,256,512. And it still has
1%~7% improvement when read percentage is greater than 50%.

The following is part of the detailed data.

posix:
qdepth=128
rw_percent      0             |           30
cpu  origin  thisPatch  opt   | origin  thisPatch opt
1	286.5	298.5	4.19%		 307	304.15	-0.93%
4	1042.5	1107	6.19%		1135.5	1136	0.04%
8	1952.5	2058	5.40%		2170.5	2170.5	0.00%
12	2658.5	2879	8.29%		3042	3046	0.13%
16	3247.5	3460.5	6.56%		3793.5	3775	-0.49%
24	4232.5	4459.5	5.36%		4614.5	4756.5	3.08%
32	4810	5095	5.93%		4488	4845	7.95%
40	5306.5	5435	2.42%		4427.5	4902	10.72%

qdepth=512
rw_percent      0             |           30
cpu  origin  thisPatch  opt   | origin  thisPatch opt
1    275	 287	4.36%		294.4	295.45	0.36%
4	 979	1041	6.33%		1073	1083.5	0.98%
8	1822.5	1914.5	5.05%		2030.5	2018.5	-0.59%
12	2441	2598.5	6.45%		2808.5	2779.5	-1.03%
16	2920.5	3109.5	6.47%		3455	3411.5	-1.26%
24	3709	3972.5	7.10%		4483.5	4502.5	0.42%
32	4225.5	4532.5	7.27%		4463.5	4733	6.04%
40	4790.5	4884.5	1.96%		4427	4904.5	10.79%

uring:
qdepth=128
rw_percent      0             |           30
cpu  origin  thisPatch  opt   | origin  thisPatch opt
1	270.5	287.5	6.28%		295.75	304.75	3.04%
4	1018.5	1089.5	6.97%		1119.5	1156.5	3.31%
8	1907	2055	7.76%		2127	2211.5	3.97%
12	2614	2801	7.15%		2982.5	3061.5	2.65%
16	3169.5	3420	7.90%		3654.5	3781.5	3.48%
24	4109.5	4414	7.41%		4691.5	4750.5	1.26%
32	4752.5	4908	3.27%		4494	4825.5	7.38%
40	5233.5	5327	1.79%		4374.5	4891	11.81%

qdepth=512
rw_percent      0             |           30
cpu  origin  thisPatch  opt   | origin  thisPatch opt
1	259.95	 276	6.17%		286.65	294.8	2.84%
4	955 	1021	6.91%		1070.5	1100	2.76%
8	1772	1903.5	7.42%		1992.5	2077.5	4.27%
12	2380.5	2543.5	6.85%		2752.5	2860	3.91%
16	2920.5	3099	6.11%		3391.5	3540	4.38%
24	3697	3912	5.82%		4401	4637	5.36%
32	4256.5	4454.5	4.65%		4516	4777	5.78%
40	4707	4968.5	5.56%		4400.5	4933	12.10%

Signed-off-by: Richael Zhuang <richael.zhuang@arm.com>
Change-Id: I730dcf89ed2bf3efe91586421a89045fc11c81f0
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/12210
Community-CI: Broadcom CI <spdk-ci.pdl@broadcom.com>
Community-CI: Mellanox Build Bot
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Aleksey Marchuk <alexeymar@nvidia.com>
Reviewed-by: Ben Walker <benjamin.walker@intel.com>
2022-04-28 07:29:28 +00:00

282 lines
9.1 KiB
C

/*-
* BSD LICENSE
*
* Copyright (c) Intel Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "spdk/stdinc.h"
#include "spdk/util.h"
#include "spdk_internal/mock.h"
#include "spdk_cunit.h"
#include "common/lib/test_env.c"
#include "sock/uring/uring.c"
DEFINE_STUB(spdk_sock_map_insert, int, (struct spdk_sock_map *map, int placement_id,
struct spdk_sock_group_impl *group), 0);
DEFINE_STUB_V(spdk_sock_map_release, (struct spdk_sock_map *map, int placement_id));
DEFINE_STUB(spdk_sock_map_lookup, int, (struct spdk_sock_map *map, int placement_id,
struct spdk_sock_group_impl **group, struct spdk_sock_group_impl *hint), 0);
DEFINE_STUB(spdk_sock_map_find_free, int, (struct spdk_sock_map *map), -1);
DEFINE_STUB_V(spdk_sock_map_cleanup, (struct spdk_sock_map *map));
DEFINE_STUB_V(spdk_net_impl_register, (struct spdk_net_impl *impl, int priority));
DEFINE_STUB(spdk_sock_close, int, (struct spdk_sock **s), 0);
DEFINE_STUB(__io_uring_get_cqe, int, (struct io_uring *ring, struct io_uring_cqe **cqe_ptr,
unsigned submit,
unsigned wait_nr, sigset_t *sigmask), 0);
DEFINE_STUB(io_uring_submit, int, (struct io_uring *ring), 0);
DEFINE_STUB(io_uring_get_sqe, struct io_uring_sqe *, (struct io_uring *ring), 0);
DEFINE_STUB(io_uring_queue_init, int, (unsigned entries, struct io_uring *ring, unsigned flags), 0);
DEFINE_STUB_V(io_uring_queue_exit, (struct io_uring *ring));
static void
_req_cb(void *cb_arg, int len)
{
*(bool *)cb_arg = true;
CU_ASSERT(len == 0);
}
static void
flush_client(void)
{
struct spdk_uring_sock_group_impl group = {};
struct spdk_uring_sock usock = {};
struct spdk_sock *sock = &usock.base;
struct spdk_sock_request *req1, *req2;
bool cb_arg1, cb_arg2;
int rc;
/* Set up data structures */
TAILQ_INIT(&sock->queued_reqs);
TAILQ_INIT(&sock->pending_reqs);
sock->group_impl = &group.base;
req1 = calloc(1, sizeof(struct spdk_sock_request) + 3 * sizeof(struct iovec));
SPDK_CU_ASSERT_FATAL(req1 != NULL);
SPDK_SOCK_REQUEST_IOV(req1, 0)->iov_base = (void *)100;
SPDK_SOCK_REQUEST_IOV(req1, 0)->iov_len = 64;
SPDK_SOCK_REQUEST_IOV(req1, 1)->iov_base = (void *)200;
SPDK_SOCK_REQUEST_IOV(req1, 1)->iov_len = 64;
SPDK_SOCK_REQUEST_IOV(req1, 2)->iov_base = (void *)300;
SPDK_SOCK_REQUEST_IOV(req1, 2)->iov_len = 64;
req1->iovcnt = 3;
req1->cb_fn = _req_cb;
req1->cb_arg = &cb_arg1;
req2 = calloc(1, sizeof(struct spdk_sock_request) + 2 * sizeof(struct iovec));
SPDK_CU_ASSERT_FATAL(req2 != NULL);
SPDK_SOCK_REQUEST_IOV(req2, 0)->iov_base = (void *)100;
SPDK_SOCK_REQUEST_IOV(req2, 0)->iov_len = 32;
SPDK_SOCK_REQUEST_IOV(req2, 1)->iov_base = (void *)200;
SPDK_SOCK_REQUEST_IOV(req2, 1)->iov_len = 32;
req2->iovcnt = 2;
req2->cb_fn = _req_cb;
req2->cb_arg = &cb_arg2;
/* Simple test - a request with a 3 element iovec
* that gets submitted in a single sendmsg. */
spdk_sock_request_queue(sock, req1);
MOCK_SET(sendmsg, 192);
cb_arg1 = false;
rc = _sock_flush_client(sock);
CU_ASSERT(rc == 0);
CU_ASSERT(cb_arg1 == true);
CU_ASSERT(TAILQ_EMPTY(&sock->queued_reqs));
/* Two requests, where both can fully send. */
spdk_sock_request_queue(sock, req1);
spdk_sock_request_queue(sock, req2);
MOCK_SET(sendmsg, 256);
cb_arg1 = false;
cb_arg2 = false;
rc = _sock_flush_client(sock);
CU_ASSERT(rc == 0);
CU_ASSERT(cb_arg1 == true);
CU_ASSERT(cb_arg2 == true);
CU_ASSERT(TAILQ_EMPTY(&sock->queued_reqs));
/* Two requests. Only first one can send */
spdk_sock_request_queue(sock, req1);
spdk_sock_request_queue(sock, req2);
MOCK_SET(sendmsg, 192);
cb_arg1 = false;
cb_arg2 = false;
rc = _sock_flush_client(sock);
CU_ASSERT(rc == 0);
CU_ASSERT(cb_arg1 == true);
CU_ASSERT(cb_arg2 == false);
CU_ASSERT(TAILQ_FIRST(&sock->queued_reqs) == req2);
TAILQ_REMOVE(&sock->queued_reqs, req2, internal.link);
CU_ASSERT(TAILQ_EMPTY(&sock->queued_reqs));
/* One request. Partial send. */
spdk_sock_request_queue(sock, req1);
MOCK_SET(sendmsg, 10);
cb_arg1 = false;
rc = _sock_flush_client(sock);
CU_ASSERT(rc == 0);
CU_ASSERT(cb_arg1 == false);
CU_ASSERT(TAILQ_FIRST(&sock->queued_reqs) == req1);
/* Do a second flush that partial sends again. */
MOCK_SET(sendmsg, 52);
cb_arg1 = false;
rc = _sock_flush_client(sock);
CU_ASSERT(rc == 0);
CU_ASSERT(cb_arg1 == false);
CU_ASSERT(TAILQ_FIRST(&sock->queued_reqs) == req1);
/* Flush the rest of the data */
MOCK_SET(sendmsg, 130);
cb_arg1 = false;
rc = _sock_flush_client(sock);
CU_ASSERT(rc == 0);
CU_ASSERT(cb_arg1 == true);
CU_ASSERT(TAILQ_EMPTY(&sock->queued_reqs));
free(req1);
free(req2);
}
static void
flush_server(void)
{
struct spdk_uring_sock_group_impl group = {};
struct spdk_uring_sock usock = {};
struct spdk_sock *sock = &usock.base;
struct spdk_sock_request *req1, *req2;
bool cb_arg1, cb_arg2;
int rc;
/* Set up data structures */
TAILQ_INIT(&sock->queued_reqs);
TAILQ_INIT(&sock->pending_reqs);
sock->group_impl = &group.base;
usock.write_task.sock = &usock;
usock.group = &group;
req1 = calloc(1, sizeof(struct spdk_sock_request) + 2 * sizeof(struct iovec));
SPDK_CU_ASSERT_FATAL(req1 != NULL);
SPDK_SOCK_REQUEST_IOV(req1, 0)->iov_base = (void *)100;
SPDK_SOCK_REQUEST_IOV(req1, 0)->iov_len = 64;
SPDK_SOCK_REQUEST_IOV(req1, 1)->iov_base = (void *)200;
SPDK_SOCK_REQUEST_IOV(req1, 1)->iov_len = 64;
req1->iovcnt = 2;
req1->cb_fn = _req_cb;
req1->cb_arg = &cb_arg1;
req2 = calloc(1, sizeof(struct spdk_sock_request) + 2 * sizeof(struct iovec));
SPDK_CU_ASSERT_FATAL(req2 != NULL);
SPDK_SOCK_REQUEST_IOV(req2, 0)->iov_base = (void *)100;
SPDK_SOCK_REQUEST_IOV(req2, 0)->iov_len = 32;
SPDK_SOCK_REQUEST_IOV(req2, 1)->iov_base = (void *)200;
SPDK_SOCK_REQUEST_IOV(req2, 1)->iov_len = 32;
req2->iovcnt = 2;
req2->cb_fn = _req_cb;
req2->cb_arg = &cb_arg2;
/* we should not call _sock_flush directly, since it will finally
* call liburing related functions */
/* Simple test - a request with a 2 element iovec
* that is fully completed. */
spdk_sock_request_queue(sock, req1);
cb_arg1 = false;
rc = spdk_sock_prep_reqs(sock, usock.write_task.iovs, 0, NULL, NULL);
CU_ASSERT(rc == 2);
sock_complete_reqs(sock, 128, 0);
CU_ASSERT(cb_arg1 == true);
CU_ASSERT(TAILQ_EMPTY(&sock->queued_reqs));
/* Two requests, where both can be fully completed. */
spdk_sock_request_queue(sock, req1);
spdk_sock_request_queue(sock, req2);
cb_arg1 = false;
cb_arg2 = false;
rc = spdk_sock_prep_reqs(sock, usock.write_task.iovs, 0, NULL, NULL);
CU_ASSERT(rc == 4);
sock_complete_reqs(sock, 192, 0);
CU_ASSERT(cb_arg1 == true);
CU_ASSERT(cb_arg2 == true);
CU_ASSERT(TAILQ_EMPTY(&sock->queued_reqs));
/* One request that is partially sent. */
spdk_sock_request_queue(sock, req1);
cb_arg1 = false;
rc = spdk_sock_prep_reqs(sock, usock.write_task.iovs, 0, NULL, NULL);
CU_ASSERT(rc == 2);
sock_complete_reqs(sock, 92, 0);
CU_ASSERT(rc == 2);
CU_ASSERT(cb_arg1 == false);
CU_ASSERT(TAILQ_FIRST(&sock->queued_reqs) == req1);
/* Get the second time partial sent result. */
sock_complete_reqs(sock, 10, 0);
CU_ASSERT(cb_arg1 == false);
CU_ASSERT(TAILQ_FIRST(&sock->queued_reqs) == req1);
/* Data is finally sent. */
sock_complete_reqs(sock, 26, 0);
CU_ASSERT(cb_arg1 == true);
CU_ASSERT(TAILQ_EMPTY(&sock->queued_reqs));
free(req1);
free(req2);
}
int
main(int argc, char **argv)
{
CU_pSuite suite = NULL;
unsigned int num_failures;
CU_set_error_action(CUEA_ABORT);
CU_initialize_registry();
suite = CU_add_suite("uring", NULL, NULL);
CU_ADD_TEST(suite, flush_client);
CU_ADD_TEST(suite, flush_server);
CU_basic_set_mode(CU_BRM_VERBOSE);
CU_basic_run_tests();
num_failures = CU_get_number_of_failures();
CU_cleanup_registry();
return num_failures;
}