API Reference Manual 1.51.0
Loading...
Searching...
No Matches
odp_stash_perf.c
1/* SPDX-License-Identifier: BSD-3-Clause
2 * Copyright (c) 2018 Linaro Limited
3 * Copyright (c) 2021-2025 Nokia
4 * Copyright (c) 2023 Arm
5 */
6
15#include <stdio.h>
16#include <string.h>
17#include <stdint.h>
18#include <inttypes.h>
19#include <stdlib.h>
20#include <getopt.h>
21
22#include <odp_api.h>
23#include <odp/helper/odph_api.h>
24
25#include <export_results.h>
26
27#define MAX_STASHES (32)
28
29typedef struct test_options_t {
30 uint32_t num_stash;
31 uint32_t num_round;
32 uint32_t max_burst;
33 uint32_t stash_size;
34 int strict;
35 int num_cpu;
36
37} test_options_t;
38
39typedef struct test_stat_t {
40 uint64_t rounds;
41 uint64_t ops;
42 uint64_t nsec;
43 uint64_t cycles;
44 uint64_t num_retry;
45
46} test_stat_t;
47
48typedef struct test_global_t {
49 odp_barrier_t barrier;
50 test_options_t options;
51 odp_instance_t instance;
52 odp_shm_t shm;
53 odp_pool_t pool;
54 odp_stash_t stash[MAX_STASHES];
55 odph_thread_t thread_tbl[ODP_THREAD_COUNT_MAX];
56 test_stat_t stat[ODP_THREAD_COUNT_MAX];
57 test_common_options_t common_options;
58
59} test_global_t;
60
61static void print_usage(void)
62{
63 printf("\n"
64 "Stash performance test\n"
65 "\n"
66 "Usage: odp_stash_perf [options]\n"
67 "\n"
68 " -c, --num_cpu <num> Number of worker threads. Default: 1\n"
69 " -n, --num_stash <num> Number of stashes. Default: 1\n"
70 " -b, --burst_size <num> Max number of objects per stash call. Default: 1\n"
71 " -s, --stash_size <num> Stash size. Default: 1000\n"
72 " -r, --num_round <num> Number of rounds. Default: 1000\n"
73 " -m, --strict Strict size stash\n"
74 " -h, --help This help\n"
75 "\n");
76}
77
78static int parse_options(int argc, char *argv[], test_options_t *test_options)
79{
80 int opt;
81 int ret = 0;
82
83 static const struct option longopts[] = {
84 { "num_cpu", required_argument, NULL, 'c' },
85 { "num_stash", required_argument, NULL, 'n' },
86 { "burst_size", required_argument, NULL, 'b' },
87 { "stash_size", required_argument, NULL, 's' },
88 { "num_round", required_argument, NULL, 'r' },
89 { "strict", no_argument, NULL, 'm' },
90 { "help", no_argument, NULL, 'h' },
91 { NULL, 0, NULL, 0 }
92 };
93
94 static const char *shortopts = "+c:n:b:s:r:mh";
95
96 test_options->num_cpu = 1;
97 test_options->num_stash = 1;
98 test_options->max_burst = 1;
99 test_options->stash_size = 1000;
100 test_options->num_round = 1000;
101 test_options->strict = 0;
102
103 while (1) {
104 opt = getopt_long(argc, argv, shortopts, longopts, NULL);
105
106 if (opt == -1)
107 break;
108
109 switch (opt) {
110 case 'c':
111 test_options->num_cpu = atoi(optarg);
112 break;
113 case 'n':
114 test_options->num_stash = atoi(optarg);
115 break;
116 case 'b':
117 test_options->max_burst = atoi(optarg);
118 break;
119 case 's':
120 test_options->stash_size = atoi(optarg);
121 break;
122 case 'r':
123 test_options->num_round = atoi(optarg);
124 break;
125 case 'm':
126 test_options->strict = 1;
127 break;
128 case 'h':
129 /* fall through */
130 default:
131 print_usage();
132 ret = -1;
133 break;
134 }
135 }
136
137 if (test_options->num_stash > MAX_STASHES) {
138 ODPH_ERR("Too many stashes %u. Test maximum %u.\n",
139 test_options->num_stash, MAX_STASHES);
140 return -1;
141 }
142
143 return ret;
144}
145
146static int create_stashes(test_global_t *global)
147{
148 uint32_t i;
149 uint32_t tmp = 0;
150 test_options_t *test_options = &global->options;
151
152 uint32_t num_stash = test_options->num_stash;
153 uint32_t num_round = test_options->num_round;
154 int num_stored;
155 uint32_t num_remain;
156 odp_stash_t *stash = global->stash;
157 odp_stash_capability_t stash_capa;
158
159 printf("\nTesting %s stashes\n",
160 test_options->strict == 0 ? "NORMAL" : "STRICT_SIZE");
161 printf(" num rounds %u\n", num_round);
162 printf(" num stashes %u\n", num_stash);
163 printf(" stash size %u\n", test_options->stash_size);
164 printf(" max burst size %u\n", test_options->max_burst);
165
167 ODPH_ERR("Get stash capability failed\n");
168 return -1;
169 }
170
171 if (test_options->stash_size > stash_capa.max_num_obj) {
172 ODPH_ERR("Max stash size supported %" PRIu64 "\n",
173 stash_capa.max_num_obj);
174 return -1;
175 }
176
177 if (test_options->num_stash > stash_capa.max_stashes) {
178 ODPH_ERR("Max stash supported %u\n", stash_capa.max_stashes);
179 return -1;
180 }
181
182 for (i = 0; i < num_stash; i++) {
183 odp_stash_param_t stash_param;
184
185 odp_stash_param_init(&stash_param);
186 stash_param.num_obj = test_options->stash_size;
187 stash_param.obj_size = sizeof(uint32_t);
188 stash_param.strict_size = test_options->strict;
189
190 stash[i] = odp_stash_create("test_stash_u32", &stash_param);
191 if (stash[i] == ODP_STASH_INVALID) {
192 ODPH_ERR("Stash create failed\n");
193 return -1;
194 }
195
196 num_remain = test_options->stash_size;
197 do {
198 num_stored = odp_stash_put_u32(stash[i], &tmp, 1);
199 if (num_stored < 0) {
200 ODPH_ERR("Error: Stash put failed\n");
201 return -1;
202 }
203 num_remain -= num_stored;
204 } while (num_remain);
205 }
206
207 return 0;
208}
209
210static int destroy_stashes(test_global_t *global)
211{
212 odp_stash_t *stash = global->stash;
213 test_options_t *test_options = &global->options;
214 uint32_t num_stash = test_options->num_stash;
215 uint32_t tmp;
216 int num;
217
218 for (uint32_t i = 0; i < num_stash; i++) {
219 do {
220 num = odp_stash_get_u32(stash[i], &tmp, 1);
221 if (num < 0) {
222 ODPH_ERR("Error: Stash get failed %u\n", i);
223 return -1;
224 }
225 } while (num);
226
227 if (odp_stash_destroy(stash[i])) {
228 ODPH_ERR("Stash destroy failed\n");
229 return -1;
230 }
231 }
232
233 return 0;
234}
235
236static int run_test(void *arg)
237{
238 uint64_t c1, c2, cycles, nsec;
239 odp_time_t t1, t2;
240 uint32_t rounds;
241 int num_stored;
242 int num_remain;
243 int num_obj;
244 test_stat_t *stat;
245 test_global_t *global = arg;
246 test_options_t *test_options = &global->options;
247 odp_stash_t stash;
248 uint64_t num_retry = 0;
249 uint64_t ops = 0;
250 uint32_t num_stash = test_options->num_stash;
251 uint32_t num_round = test_options->num_round;
252 int thr = odp_thread_id();
253 int ret = 0;
254 uint32_t i = 0;
255 uint32_t max_burst = test_options->max_burst;
256 uint32_t *tmp = malloc(sizeof(uint32_t) * max_burst);
257
258 if (tmp == NULL) {
259 ODPH_ERR("Error: malloc failed\n");
260 ret = -1;
261 goto error;
262 }
263
264 stat = &global->stat[thr];
265
266 /* Start all workers at the same time */
267 odp_barrier_wait(&global->barrier);
268
269 t1 = odp_time_local();
270 c1 = odp_cpu_cycles();
271
272 for (rounds = 0; rounds < num_round; rounds++) {
273 stash = global->stash[i++];
274
275 if (i == num_stash)
276 i = 0;
277
278 num_obj = odp_stash_get_u32(stash, tmp, max_burst);
279 if (num_obj == 0)
280 continue;
281
282 if (num_obj < 0) {
283 ODPH_ERR("Error: Stash get failed\n");
284 ret = -1;
285 goto error;
286 }
287 num_remain = num_obj;
288 do {
289 num_stored = odp_stash_put_u32(stash, tmp, num_remain);
290 if (num_stored < 0) {
291 ODPH_ERR("Error: Stash put failed\n");
292 ret = -1;
293 goto error;
294 }
295
296 if (num_stored != num_remain)
297 num_retry++;
298
299 num_remain -= num_stored;
300 } while (num_remain);
301 ops += num_obj;
302 }
303
304 c2 = odp_cpu_cycles();
305 t2 = odp_time_local();
306
307 nsec = odp_time_diff_ns(t2, t1);
308 cycles = odp_cpu_cycles_diff(c2, c1);
309
310 stat->rounds = rounds;
311 stat->ops = ops;
312 stat->nsec = nsec;
313 stat->cycles = cycles;
314 stat->num_retry = num_retry;
315error:
316 free(tmp);
317 return ret;
318}
319
320static int start_workers(test_global_t *global)
321{
322 odph_thread_common_param_t thr_common;
323 odph_thread_param_t thr_param;
324 odp_cpumask_t cpumask;
325 int ret;
326 test_options_t *test_options = &global->options;
327 int num_cpu = test_options->num_cpu;
328
329 ret = odp_cpumask_default_worker(&cpumask, num_cpu);
330
331 if (num_cpu && ret != num_cpu) {
332 ODPH_ERR("Error: Too many workers. Max supported %i\n.", ret);
333 return -1;
334 }
335
336 /* Zero: all available workers */
337 if (num_cpu == 0) {
338 num_cpu = ret;
339 test_options->num_cpu = num_cpu;
340 }
341
342 printf(" num workers %u\n\n", num_cpu);
343
344 odp_barrier_init(&global->barrier, num_cpu);
345
346 odph_thread_common_param_init(&thr_common);
347 thr_common.instance = global->instance;
348 thr_common.cpumask = &cpumask;
349 thr_common.share_param = 1;
350
351 odph_thread_param_init(&thr_param);
352 thr_param.start = run_test;
353 thr_param.arg = global;
354 thr_param.thr_type = ODP_THREAD_WORKER;
355
356 if (odph_thread_create(global->thread_tbl, &thr_common, &thr_param,
357 num_cpu) != num_cpu)
358 return -1;
359
360 return 0;
361}
362
363static int output_results(test_global_t *global)
364{
365 int i, num;
366 double rounds_ave, ops_ave, nsec_ave, cycles_ave, retry_ave;
367 test_options_t *test_options = &global->options;
368 int num_cpu = test_options->num_cpu;
369 uint64_t rounds_sum = 0;
370 uint64_t ops_sum = 0;
371 uint64_t nsec_sum = 0;
372 uint64_t cycles_sum = 0;
373 uint64_t retry_sum = 0;
374
375 /* Averages */
376 for (i = 0; i < ODP_THREAD_COUNT_MAX; i++) {
377 rounds_sum += global->stat[i].rounds;
378 ops_sum += global->stat[i].ops;
379 nsec_sum += global->stat[i].nsec;
380 cycles_sum += global->stat[i].cycles;
381 retry_sum += global->stat[i].num_retry;
382 }
383
384 if (rounds_sum == 0) {
385 printf("No results.\n");
386 return 0;
387 }
388
389 rounds_ave = rounds_sum / num_cpu;
390 ops_ave = ops_sum / num_cpu;
391 nsec_ave = nsec_sum / num_cpu;
392 cycles_ave = cycles_sum / num_cpu;
393 retry_ave = retry_sum / num_cpu;
394 num = 0;
395
396 printf("RESULTS - per thread (Million ops per sec):\n");
397 printf("----------------------------------------------\n");
398 printf(" 1 2 3 4 5 6 7 8 9 10");
399
400 for (i = 0; i < ODP_THREAD_COUNT_MAX; i++) {
401 if (global->stat[i].rounds) {
402 if ((num % 10) == 0)
403 printf("\n ");
404
405 printf("%6.1f ", (1000.0 * global->stat[i].ops) /
406 global->stat[i].nsec);
407 num++;
408 }
409 }
410 printf("\n\n");
411
412 printf("RESULTS - per thread average (%i threads):\n", num_cpu);
413 printf("------------------------------------------\n");
414 printf(" duration: %.3f msec\n", nsec_ave / 1000000);
415 printf(" num cycles: %.3f M\n", cycles_ave / 1000000);
416 printf(" ops per get: %.3f\n", ops_ave / rounds_ave);
417 printf(" cycles per ops: %.3f\n", cycles_ave / ops_ave);
418 printf(" retries per sec: %.3f k\n",
419 (1000000.0 * retry_ave) / nsec_ave);
420 printf(" ops per sec: %.3f M\n\n",
421 (1000.0 * ops_ave) / nsec_ave);
422
423 printf("TOTAL ops per sec: %.3f M\n\n",
424 (1000.0 * ops_sum) / nsec_ave);
425
426 if (global->common_options.is_export) {
427 if (test_common_write("duration (msec),num cycles (M),ops per get,"
428 "cycles per ops,retries per sec (k),ops per sec (M),"
429 "total ops per sec (M)\n")) {
430 ODPH_ERR("Export failed\n");
431 test_common_write_term();
432 return -1;
433 }
434
435 if (test_common_write("%f,%f,%f,%f,%f,%f,%f\n",
436 nsec_ave / 1000000, cycles_ave / 1000000,
437 ops_ave / rounds_ave, cycles_ave / ops_ave,
438 (1000000.0 * retry_ave) / nsec_ave,
439 (1000.0 * ops_ave) / nsec_ave,
440 (1000.0 * ops_sum) / nsec_ave)) {
441 ODPH_ERR("Export failed\n");
442 test_common_write_term();
443 return -1;
444 }
445
446 test_common_write_term();
447 }
448
449 return 0;
450}
451
452int main(int argc, char **argv)
453{
454 odph_helper_options_t helper_options;
455 odp_instance_t instance;
456 odp_init_t init;
457 odp_shm_t shm;
458 test_global_t *global;
459 test_common_options_t common_options;
460 int ret = 0;
461
462 /* Let helper collect its own arguments (e.g. --odph_proc) */
463 argc = odph_parse_options(argc, argv);
464 if (odph_options(&helper_options)) {
465 ODPH_ERR("Error: Reading ODP helper options failed.\n");
466 exit(EXIT_FAILURE);
467 }
468
469 argc = test_common_parse_options(argc, argv);
470 if (test_common_options(&common_options)) {
471 ODPH_ERR("Error: Reading test options failed\n");
472 exit(EXIT_FAILURE);
473 }
474
475 /* List features not to be used */
476 odp_init_param_init(&init);
477 init.not_used.feat.cls = 1;
478 init.not_used.feat.compress = 1;
479 init.not_used.feat.crypto = 1;
480 init.not_used.feat.ipsec = 1;
481 init.not_used.feat.schedule = 1;
482 init.not_used.feat.timer = 1;
483 init.not_used.feat.tm = 1;
484
485 init.mem_model = helper_options.mem_model;
486
487 /* Init ODP before calling anything else */
488 if (odp_init_global(&instance, &init, NULL)) {
489 ODPH_ERR("Error: Global init failed.\n");
490 exit(EXIT_FAILURE);
491 }
492
493 /* Init this thread */
494 if (odp_init_local(instance, ODP_THREAD_WORKER)) {
495 ODPH_ERR("Error: Local init failed.\n");
496 exit(EXIT_FAILURE);
497 }
498
499 shm = odp_shm_reserve("stash_perf_global", sizeof(test_global_t),
500 ODP_CACHE_LINE_SIZE, 0);
501 if (shm == ODP_SHM_INVALID) {
502 ODPH_ERR("Error: Shared mem reserve failed.\n");
503 exit(EXIT_FAILURE);
504 }
505
506 global = odp_shm_addr(shm);
507 if (global == NULL) {
508 ODPH_ERR("Error: Shared mem alloc failed\n");
509 exit(EXIT_FAILURE);
510 }
511
512 memset(global, 0, sizeof(test_global_t));
513
514 global->common_options = common_options;
515
516 if (parse_options(argc, argv, &global->options))
517 exit(EXIT_FAILURE);
518
520
521 global->instance = instance;
522
523 if (create_stashes(global)) {
524 ODPH_ERR("Error: Create stashes failed.\n");
525 ret = -1;
526 goto destroy;
527 }
528
529 if (start_workers(global)) {
530 ODPH_ERR("Error: Test start failed.\n");
531 ret = -1;
532 goto destroy;
533 }
534
535 /* Wait workers to exit */
536 odph_thread_join(global->thread_tbl, global->options.num_cpu);
537
538 if (output_results(global)) {
539 ret = -1;
540 goto destroy;
541 }
542
543destroy:
544 if (destroy_stashes(global)) {
545 ODPH_ERR("Error: Destroy stashes failed.\n");
546 exit(EXIT_FAILURE);
547 }
548
549 if (odp_shm_free(shm)) {
550 ODPH_ERR("Error: Shared mem free failed.\n");
551 exit(EXIT_FAILURE);
552 }
553
554 if (odp_term_local()) {
555 ODPH_ERR("Error: term local failed.\n");
556 exit(EXIT_FAILURE);
557 }
558
559 if (odp_term_global(instance)) {
560 ODPH_ERR("Error: term global failed.\n");
561 exit(EXIT_FAILURE);
562 }
563
564 return ret;
565}
void odp_barrier_init(odp_barrier_t *barr, int count)
Initialize barrier with thread count.
void odp_barrier_wait(odp_barrier_t *barr)
Synchronize thread execution on barrier.
uint64_t odp_cpu_cycles_diff(uint64_t c2, uint64_t c1)
CPU cycle count difference.
uint64_t odp_cpu_cycles(void)
Current CPU cycle count.
int odp_cpumask_default_worker(odp_cpumask_t *mask, int num)
Default CPU mask for worker threads.
void odp_init_param_init(odp_init_t *param)
Initialize the odp_init_t to default values for all fields.
int odp_init_local(odp_instance_t instance, odp_thread_type_t thr_type)
Thread local ODP initialization.
int odp_init_global(odp_instance_t *instance, const odp_init_t *params, const odp_platform_init_t *platform_params)
Global ODP initialization.
int odp_term_local(void)
Thread local ODP termination.
int odp_term_global(odp_instance_t instance)
Global ODP termination.
uint64_t odp_instance_t
ODP instance ID.
int odp_shm_free(odp_shm_t shm)
Free a contiguous block of shared memory.
void * odp_shm_addr(odp_shm_t shm)
Shared memory block address.
#define ODP_SHM_INVALID
Invalid shared memory block.
odp_shm_t odp_shm_reserve(const char *name, uint64_t size, uint64_t align, uint32_t flags)
Reserve a contiguous block of shared memory.
#define ODP_STASH_INVALID
Invalid stash handle.
void odp_stash_param_init(odp_stash_param_t *param)
Initialize stash params.
int odp_stash_destroy(odp_stash_t stash)
Destroy a stash.
int32_t odp_stash_put_u32(odp_stash_t stash, const uint32_t val[], int32_t num)
Put 32-bit integers into a stash.
int odp_stash_capability(odp_stash_capability_t *capa, odp_stash_type_t type)
Query stash capabilities.
int32_t odp_stash_get_u32(odp_stash_t stash, uint32_t val[], int32_t num)
Get 32-bit integers from a stash.
odp_stash_t odp_stash_create(const char *name, const odp_stash_param_t *param)
Create a stash.
@ ODP_STASH_TYPE_DEFAULT
The default stash type.
void odp_sys_info_print(void)
Print system info.
#define ODP_THREAD_COUNT_MAX
Maximum number of threads supported in build time.
int odp_thread_id(void)
Get thread identifier.
@ ODP_THREAD_WORKER
Worker thread.
odp_time_t odp_time_local(void)
Current local time.
uint64_t odp_time_diff_ns(odp_time_t t2, odp_time_t t1)
Time difference in nanoseconds.
The OpenDataPlane API.
Global initialization parameters.
odp_mem_model_t mem_model
Application memory model.
odp_feature_t not_used
Unused features.
Stash capabilities (per stash type)
uint32_t max_stashes
Maximum number of stashes of this type.
uint64_t max_num_obj
Maximum common number of object handles per stash for any object size.
odp_bool_t strict_size
Strict size.
uint32_t obj_size
Object handle size in bytes.
uint64_t num_obj
Number of object handles.
uint32_t tm
Traffic Manager APIs, e.g., odp_tm_xxx()
uint32_t crypto
Crypto APIs, e.g., odp_crypto_xxx()
uint32_t ipsec
IPsec APIs, e.g., odp_ipsec_xxx()
uint32_t timer
Timer APIs, e.g., odp_timer_xxx(), odp_timeout_xxx()
uint32_t cls
Classifier APIs, e.g., odp_cls_xxx(), odp_cos_xxx()
struct odp_feature_t::@174 feat
Individual feature bits.
uint32_t schedule
Scheduler APIs, e.g., odp_schedule_xxx()
uint32_t compress
Compression APIs, e.g., odp_comp_xxx()