diff options
Diffstat (limited to 'test/performance/odp_queue_perf.c')
-rw-r--r-- | test/performance/odp_queue_perf.c | 649 |
1 files changed, 649 insertions, 0 deletions
diff --git a/test/performance/odp_queue_perf.c b/test/performance/odp_queue_perf.c new file mode 100644 index 000000000..153f87d10 --- /dev/null +++ b/test/performance/odp_queue_perf.c @@ -0,0 +1,649 @@ +/* SPDX-License-Identifier: BSD-3-Clause + * Copyright (c) 2018 Linaro Limited + * Copyright (c) 2021-2023 Nokia + */ + +/** + * @example odp_queue_perf.c + * + * Performance test application for queue APIs + * + * @cond _ODP_HIDE_FROM_DOXYGEN_ + */ + +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <inttypes.h> +#include <stdlib.h> +#include <getopt.h> + +#include <odp_api.h> +#include <odp/helper/odph_api.h> + +#define MAX_QUEUES (32 * 1024) + +typedef struct test_options_t { + uint32_t num_queue; + uint32_t num_event; + uint32_t num_round; + uint32_t max_burst; + odp_nonblocking_t nonblock; + int single; + int num_cpu; + +} test_options_t; + +typedef struct test_stat_t { + uint64_t rounds; + uint64_t events; + uint64_t nsec; + uint64_t cycles; + uint64_t deq_retry; + uint64_t enq_retry; + +} test_stat_t; + +typedef struct test_global_t { + odp_barrier_t barrier; + test_options_t options; + odp_instance_t instance; + odp_shm_t shm; + odp_pool_t pool; + odp_queue_t queue[MAX_QUEUES]; + odph_thread_t thread_tbl[ODP_THREAD_COUNT_MAX]; + test_stat_t stat[ODP_THREAD_COUNT_MAX]; + +} test_global_t; + +static void print_usage(void) +{ + printf("\n" + "Plain queue performance test\n" + "\n" + "Usage: odp_queue_perf [options]\n" + "\n" + " -c, --num_cpu Number of worker threads. Default: 1\n" + " -q, --num_queue Number of queues. Default: 1\n" + " -e, --num_event Number of events per queue. Default: 1\n" + " -b, --burst_size Maximum number of events per operation. Default: 1\n" + " -r, --num_round Number of rounds\n" + " -l, --lockfree Lockfree queues\n" + " -w, --waitfree Waitfree queues\n" + " -s, --single Single producer, single consumer\n" + " -h, --help This help\n" + "\n"); +} + +static int parse_options(int argc, char *argv[], test_options_t *test_options) +{ + int opt; + int long_index; + int ret = 0; + + static const struct option longopts[] = { + {"num_cpu", required_argument, NULL, 'c'}, + {"num_queue", required_argument, NULL, 'q'}, + {"num_event", required_argument, NULL, 'e'}, + {"burst_size", required_argument, NULL, 'b'}, + {"num_round", required_argument, NULL, 'r'}, + {"lockfree", no_argument, NULL, 'l'}, + {"waitfree", no_argument, NULL, 'w'}, + {"single", no_argument, NULL, 's'}, + {"help", no_argument, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + static const char *shortopts = "+c:q:e:b:r:lwsh"; + + test_options->num_cpu = 1; + test_options->num_queue = 1; + test_options->num_event = 1; + test_options->max_burst = 1; + test_options->num_round = 1000; + test_options->nonblock = ODP_BLOCKING; + test_options->single = 0; + + while (1) { + opt = getopt_long(argc, argv, shortopts, longopts, &long_index); + + if (opt == -1) + break; + + switch (opt) { + case 'c': + test_options->num_cpu = atoi(optarg); + break; + case 'q': + test_options->num_queue = atoi(optarg); + break; + case 'e': + test_options->num_event = atoi(optarg); + break; + case 'b': + test_options->max_burst = atoi(optarg); + break; + case 'r': + test_options->num_round = atoi(optarg); + break; + case 'l': + test_options->nonblock = ODP_NONBLOCKING_LF; + break; + case 'w': + test_options->nonblock = ODP_NONBLOCKING_WF; + break; + case 's': + test_options->single = 1; + break; + case 'h': + /* fall through */ + default: + print_usage(); + ret = -1; + break; + } + } + + if (test_options->num_queue > MAX_QUEUES) { + printf("Too many queues %u. Test maximum %u.\n", + test_options->num_queue, MAX_QUEUES); + return -1; + } + + return ret; +} + +static int create_queues(test_global_t *global) +{ + odp_pool_capability_t pool_capa; + odp_queue_capability_t queue_capa; + odp_pool_param_t pool_param; + odp_queue_param_t queue_param; + odp_pool_t pool; + uint32_t i, j, max_size, max_num; + test_options_t *test_options = &global->options; + odp_nonblocking_t nonblock = test_options->nonblock; + uint32_t num_queue = test_options->num_queue; + uint32_t num_event = test_options->num_event; + uint32_t num_round = test_options->num_round; + uint32_t tot_event = num_queue * num_event; + int ret = 0; + odp_queue_t *queue = global->queue; + odp_event_t event[tot_event]; + + printf("\nTesting %s queues\n", + nonblock == ODP_BLOCKING ? "NORMAL" : + (nonblock == ODP_NONBLOCKING_LF ? "LOCKFREE" : + (nonblock == ODP_NONBLOCKING_WF ? "WAITFREE" : "???"))); + printf(" num rounds %u\n", num_round); + printf(" num queues %u\n", num_queue); + printf(" num events per queue %u\n", num_event); + printf(" max burst size %u\n", test_options->max_burst); + + for (i = 0; i < num_queue; i++) + queue[i] = ODP_QUEUE_INVALID; + + for (i = 0; i < tot_event; i++) + event[i] = ODP_EVENT_INVALID; + + if (odp_queue_capability(&queue_capa)) { + printf("Error: Queue capa failed.\n"); + return -1; + } + + if (odp_pool_capability(&pool_capa)) { + printf("Error: Pool capa failed.\n"); + return -1; + } + + if (nonblock == ODP_BLOCKING) { + if (num_queue > queue_capa.plain.max_num) { + printf("Max queues supported %u\n", + queue_capa.plain.max_num); + return -1; + } + + max_size = queue_capa.plain.max_size; + if (max_size && num_event > max_size) { + printf("Max queue size supported %u\n", max_size); + return -1; + } + } else if (nonblock == ODP_NONBLOCKING_LF) { + if (queue_capa.plain.lockfree.max_num == 0) { + printf("Lockfree queues not supported\n"); + return -1; + } + + if (num_queue > queue_capa.plain.lockfree.max_num) { + printf("Max lockfree queues supported %u\n", + queue_capa.plain.lockfree.max_num); + return -1; + } + + max_size = queue_capa.plain.lockfree.max_size; + if (max_size && num_event > max_size) { + printf("Max lockfree queue size supported %u\n", + max_size); + return -1; + } + } else if (nonblock == ODP_NONBLOCKING_WF) { + if (queue_capa.plain.waitfree.max_num == 0) { + printf("Waitfree queues not supported\n"); + return -1; + } + + if (num_queue > queue_capa.plain.waitfree.max_num) { + printf("Max waitfree queues supported %u\n", + queue_capa.plain.waitfree.max_num); + return -1; + } + + max_size = queue_capa.plain.waitfree.max_size; + if (max_size && num_event > max_size) { + printf("Max waitfree queue size supported %u\n", + max_size); + return -1; + } + } else { + printf("Error: Bad queue blocking type\n"); + return -1; + } + + max_num = pool_capa.buf.max_num; + + if (max_num && tot_event > max_num) { + printf("Error: max events supported %u\n", max_num); + return -1; + } + + odp_pool_param_init(&pool_param); + pool_param.type = ODP_POOL_BUFFER; + pool_param.buf.num = tot_event; + + pool = odp_pool_create("queue perf pool", &pool_param); + + if (pool == ODP_POOL_INVALID) { + printf("Error: Pool create failed.\n"); + return -1; + } + + global->pool = pool; + + odp_queue_param_init(&queue_param); + queue_param.type = ODP_QUEUE_TYPE_PLAIN; + queue_param.nonblocking = nonblock; + queue_param.size = num_event; + + if (test_options->single) { + queue_param.enq_mode = ODP_QUEUE_OP_MT_UNSAFE; + queue_param.deq_mode = ODP_QUEUE_OP_MT_UNSAFE; + } + + for (i = 0; i < num_queue; i++) { + queue[i] = odp_queue_create(NULL, &queue_param); + + if (queue[i] == ODP_QUEUE_INVALID) { + printf("Error: Queue create failed %u.\n", i); + return -1; + } + } + + for (i = 0; i < tot_event; i++) { + event[i] = odp_buffer_to_event(odp_buffer_alloc(pool)); + + if (event[i] == ODP_EVENT_INVALID) { + printf("Error: Event alloc failed %u.\n", i); + ret = -1; + goto free_events; + } + } + + for (i = 0; i < num_queue; i++) { + for (j = 0; j < num_event; j++) { + uint32_t id = i * num_event + j; + + if (odp_queue_enq(queue[i], event[id])) { + printf("Error: Queue enq failed %u/%u\n", i, j); + ret = -1; + goto free_events; + } + + event[id] = ODP_EVENT_INVALID; + } + } + +free_events: + /* Free events that were not stored into queues */ + for (i = 0; i < tot_event; i++) { + if (event[i] != ODP_EVENT_INVALID) + odp_event_free(event[i]); + } + + return ret; +} + +static int destroy_queues(test_global_t *global) +{ + odp_event_t ev; + uint32_t i, j; + int ret = 0; + test_options_t *test_options = &global->options; + uint32_t num_queue = test_options->num_queue; + uint32_t num_event = test_options->num_event; + odp_queue_t *queue = global->queue; + odp_pool_t pool = global->pool; + + for (i = 0; i < num_queue; i++) { + if (queue[i] == ODP_QUEUE_INVALID) { + printf("Error: Invalid queue handle (i: %u).\n", i); + break; + } + + for (j = 0; j < num_event; j++) { + ev = odp_queue_deq(queue[i]); + + if (ev != ODP_EVENT_INVALID) + odp_event_free(ev); + } + + if (odp_queue_destroy(queue[i])) { + printf("Error: Queue destroy failed %u.\n", i); + ret = -1; + break; + } + } + + if (pool != ODP_POOL_INVALID && odp_pool_destroy(pool)) { + printf("Error: Pool destroy failed.\n"); + ret = -1; + } + + return ret; +} + +static int run_test(void *arg) +{ + uint64_t c1, c2, cycles, nsec; + odp_time_t t1, t2; + uint32_t rounds; + int num_ev; + test_stat_t *stat; + test_global_t *global = arg; + test_options_t *test_options = &global->options; + odp_queue_t queue; + uint64_t num_deq_retry = 0; + uint64_t num_enq_retry = 0; + uint64_t events = 0; + uint32_t num_queue = test_options->num_queue; + uint32_t num_round = test_options->num_round; + int thr = odp_thread_id(); + int ret = 0; + uint32_t i = 0; + uint32_t max_burst = test_options->max_burst; + odp_event_t ev[max_burst]; + + stat = &global->stat[thr]; + + /* Start all workers at the same time */ + odp_barrier_wait(&global->barrier); + + t1 = odp_time_local(); + c1 = odp_cpu_cycles(); + + for (rounds = 0; rounds < num_round; rounds++) { + int num_enq = 0; + + do { + queue = global->queue[i++]; + + if (i == num_queue) + i = 0; + + num_ev = odp_queue_deq_multi(queue, ev, max_burst); + + if (odp_unlikely(num_ev < 0)) + ODPH_ABORT("odp_queue_deq_multi() failed\n"); + + if (odp_unlikely(num_ev == 0)) + num_deq_retry++; + + } while (num_ev == 0); + + while (num_enq < num_ev) { + int num = odp_queue_enq_multi(queue, &ev[num_enq], num_ev - num_enq); + + if (odp_unlikely(num < 0)) + ODPH_ABORT("odp_queue_enq_multi() failed\n"); + + num_enq += num; + + if (odp_unlikely(num_enq != num_ev)) + num_enq_retry++; + } + events += num_ev; + } + + c2 = odp_cpu_cycles(); + t2 = odp_time_local(); + + nsec = odp_time_diff_ns(t2, t1); + cycles = odp_cpu_cycles_diff(c2, c1); + + stat->rounds = rounds; + stat->events = events; + stat->nsec = nsec; + stat->cycles = cycles; + stat->deq_retry = num_deq_retry; + stat->enq_retry = num_enq_retry; + + return ret; +} + +static int start_workers(test_global_t *global) +{ + odph_thread_common_param_t thr_common; + odph_thread_param_t thr_param; + odp_cpumask_t cpumask; + int ret; + test_options_t *test_options = &global->options; + int num_cpu = test_options->num_cpu; + + ret = odp_cpumask_default_worker(&cpumask, num_cpu); + + if (num_cpu && ret != num_cpu) { + printf("Error: Too many workers. Max supported %i\n.", ret); + return -1; + } + + /* Zero: all available workers */ + if (num_cpu == 0) { + num_cpu = ret; + test_options->num_cpu = num_cpu; + } + + printf(" num workers %u\n\n", num_cpu); + + odp_barrier_init(&global->barrier, num_cpu); + + odph_thread_common_param_init(&thr_common); + thr_common.instance = global->instance; + thr_common.cpumask = &cpumask; + thr_common.share_param = 1; + + odph_thread_param_init(&thr_param); + thr_param.start = run_test; + thr_param.arg = global; + thr_param.thr_type = ODP_THREAD_WORKER; + + if (odph_thread_create(global->thread_tbl, &thr_common, &thr_param, + num_cpu) != num_cpu) + return -1; + + return 0; +} + +static void print_stat(test_global_t *global) +{ + int i, num; + double rounds_ave, events_ave, nsec_ave, cycles_ave; + test_options_t *test_options = &global->options; + int num_cpu = test_options->num_cpu; + uint64_t rounds_sum = 0; + uint64_t events_sum = 0; + uint64_t nsec_sum = 0; + uint64_t cycles_sum = 0; + uint64_t deq_retry_sum = 0; + uint64_t enq_retry_sum = 0; + + /* Averages */ + for (i = 0; i < ODP_THREAD_COUNT_MAX; i++) { + rounds_sum += global->stat[i].rounds; + events_sum += global->stat[i].events; + nsec_sum += global->stat[i].nsec; + cycles_sum += global->stat[i].cycles; + deq_retry_sum += global->stat[i].deq_retry; + enq_retry_sum += global->stat[i].enq_retry; + } + + if (rounds_sum == 0) { + printf("No results.\n"); + return; + } + + rounds_ave = rounds_sum / num_cpu; + events_ave = events_sum / num_cpu; + nsec_ave = nsec_sum / num_cpu; + cycles_ave = cycles_sum / num_cpu; + num = 0; + + printf("RESULTS - per thread (Million events per sec):\n"); + printf("----------------------------------------------\n"); + printf(" 1 2 3 4 5 6 7 8 9 10"); + + for (i = 0; i < ODP_THREAD_COUNT_MAX; i++) { + if (global->stat[i].rounds) { + if ((num % 10) == 0) + printf("\n "); + + printf("%6.1f ", (1000.0 * global->stat[i].events) / + global->stat[i].nsec); + num++; + } + } + printf("\n\n"); + + printf("RESULTS - per thread average (%i threads):\n", num_cpu); + printf("------------------------------------------\n"); + printf(" duration: %.3f msec\n", nsec_ave / 1000000); + printf(" num cycles: %.3f M\n", cycles_ave / 1000000); + printf(" events per dequeue: %.3f\n", + events_ave / rounds_ave); + printf(" cycles per event: %.3f\n", + cycles_ave / events_ave); + printf(" dequeue retries: %" PRIu64 "\n", deq_retry_sum); + printf(" enqueue retries: %" PRIu64 "\n", enq_retry_sum); + printf(" events per sec: %.3f M\n\n", + (1000.0 * events_ave) / nsec_ave); + + printf("TOTAL events per sec: %.3f M\n\n", + (1000.0 * events_sum) / nsec_ave); +} + +int main(int argc, char **argv) +{ + odph_helper_options_t helper_options; + odp_instance_t instance; + odp_init_t init; + odp_shm_t shm; + test_global_t *global; + + /* Let helper collect its own arguments (e.g. --odph_proc) */ + argc = odph_parse_options(argc, argv); + if (odph_options(&helper_options)) { + ODPH_ERR("Error: Reading ODP helper options failed.\n"); + exit(EXIT_FAILURE); + } + + /* List features not to be used */ + odp_init_param_init(&init); + init.not_used.feat.cls = 1; + init.not_used.feat.compress = 1; + init.not_used.feat.crypto = 1; + init.not_used.feat.ipsec = 1; + init.not_used.feat.schedule = 1; + init.not_used.feat.timer = 1; + init.not_used.feat.tm = 1; + + init.mem_model = helper_options.mem_model; + + /* Init ODP before calling anything else */ + if (odp_init_global(&instance, &init, NULL)) { + printf("Error: Global init failed.\n"); + return -1; + } + + /* Init this thread */ + if (odp_init_local(instance, ODP_THREAD_WORKER)) { + printf("Error: Local init failed.\n"); + return -1; + } + + shm = odp_shm_reserve("queue_perf_global", sizeof(test_global_t), ODP_CACHE_LINE_SIZE, 0); + if (shm == ODP_SHM_INVALID) { + ODPH_ERR("Error: Shared mem reserve failed.\n"); + exit(EXIT_FAILURE); + } + + global = odp_shm_addr(shm); + if (global == NULL) { + ODPH_ERR("Error: Shared mem alloc failed\n"); + exit(EXIT_FAILURE); + } + + memset(global, 0, sizeof(test_global_t)); + + if (parse_options(argc, argv, &global->options)) + return -1; + + odp_sys_info_print(); + + global->instance = instance; + + if (create_queues(global)) { + printf("Error: Create queues failed.\n"); + goto destroy; + } + + if (start_workers(global)) { + printf("Error: Test start failed.\n"); + return -1; + } + + /* Wait workers to exit */ + odph_thread_join(global->thread_tbl, global->options.num_cpu); + + print_stat(global); + +destroy: + if (destroy_queues(global)) { + printf("Error: Destroy queues failed.\n"); + return -1; + } + + if (odp_shm_free(shm)) { + ODPH_ERR("Error: Shared mem free failed.\n"); + exit(EXIT_FAILURE); + } + + if (odp_term_local()) { + printf("Error: term local failed.\n"); + return -1; + } + + if (odp_term_global(instance)) { + printf("Error: term global failed.\n"); + return -1; + } + + return 0; +} |