21#include <odp/helper/odph_api.h>
24#define MAX_STASH_RETRY 100
27#define MIN_BLOCK_LEN 1024
29typedef struct test_options_t {
34 uint32_t shared_blocks;
40typedef struct test_global_t test_global_t;
42typedef struct test_thread_ctx_t {
43 test_global_t *global;
51 test_options_t test_options;
65static void print_usage(
void)
68 "Memory performance test\n"
70 "Usage: odp_mem_perf [options]\n"
72 " -c, --num_cpu Number of CPUs (worker threads). 0: all available CPUs. Default 1.\n"
73 " -r, --num_round Number of rounds. Default 1000.\n"
74 " -l, --data_len Data length in bytes. Default 10MB.\n"
75 " -f, --flags SHM flags parameter. Default 0.\n"
76 " -p, --private 0: Shared memory. The area is split into blocks stored in a stash.\n"
77 " Threads share the memory, and each block is used by one\n"
78 " thread at a time (default).\n"
79 " 1: Memory areas are private to each thread. This increases\n"
80 " memory consumption to num_cpu * data_len.\n"
81 " -s, --shared_blocks Number of memory blocks in shared memory mode. The shared\n"
82 " area is split into this many equal, cache line aligned blocks.\n"
83 " Default: 2 x num_cpu. Minimum: num_cpu. Maximum: data_len / %u\n"
84 " (i.e. min block size).\n"
85 " -m, --mode 0: Memset data with varying value (default)\n"
86 " 1: Memset data with zero\n"
87 " 2: Memcpy data. On each round, reads data from one half of the memory area\n"
88 " and writes it to the other half.\n"
89 " 3: Read data. On each round, reads through the entire memory area.\n"
90 " 4: Memcmp data. On each round, compares data of one half of the memory\n"
91 " area to the other half.\n"
92 " -h, --help This help\n"
96static int parse_options(
int argc,
char *argv[], test_options_t *test_options)
101 static const struct option longopts[] = {
102 {
"num_cpu", required_argument, NULL,
'c'},
103 {
"num_round", required_argument, NULL,
'r'},
104 {
"data_len", required_argument, NULL,
'l'},
105 {
"flags", required_argument, NULL,
'f'},
106 {
"private", required_argument, NULL,
'p'},
107 {
"shared_blocks", required_argument, NULL,
's'},
108 {
"mode", required_argument, NULL,
'm'},
109 {
"help", no_argument, NULL,
'h'},
113 static const char *shortopts =
"+c:r:l:f:p:s:m:h";
115 test_options->num_cpu = 1;
116 test_options->num_round = 1000;
117 test_options->data_len = 10 * 1024 * 1024;
118 test_options->shm_flags = 0;
119 test_options->shared_blocks = 0;
120 test_options->private = 0;
121 test_options->mode = 0;
124 opt = getopt_long(argc, argv, shortopts, longopts, NULL);
131 test_options->num_cpu = atoi(optarg);
134 test_options->num_round = atoi(optarg);
137 test_options->data_len = strtoull(optarg, NULL, 0);
140 test_options->shm_flags = strtoul(optarg, NULL, 0);
143 test_options->private = atoi(optarg);
146 test_options->shared_blocks = strtoul(optarg, NULL, 0);
149 test_options->mode = atoi(optarg);
160 if (test_options->mode < 0 || test_options->mode > 4) {
161 ODPH_ERR(
"Bad mode: %i\n", test_options->mode);
168static int set_num_cpu(test_global_t *global)
171 test_options_t *test_options = &global->test_options;
172 int num_cpu = test_options->num_cpu;
186 if (num_cpu && ret != num_cpu) {
187 ODPH_ERR(
"Too many workers. Max supported %i.\n", ret);
194 ODPH_ERR(
"Too many cpus from odp_cpumask_default_worker(): %i\n", ret);
199 test_options->num_cpu = num_cpu;
208static int set_shared_blocks(test_global_t *global)
210 test_options_t *opt = &global->test_options;
211 uint32_t num_cpu = opt->num_cpu;
218 max_blocks = opt->data_len / MIN_BLOCK_LEN;
219 if (max_blocks < num_cpu) {
220 ODPH_ERR(
"Data length %" PRIu64
" is too small for %u threads. "
221 "At least %" PRIu64
" bytes needed.\n",
222 opt->data_len, num_cpu, (uint64_t)num_cpu * MIN_BLOCK_LEN);
227 if (opt->shared_blocks == 0) {
228 if ((uint64_t)num_cpu * 2 > max_blocks) {
229 ODPH_ERR(
"Default shared block count %u exceeds maximum %" PRIu64
". "
230 "Increase data length or set --shared_blocks.\n",
231 num_cpu * 2, max_blocks);
235 opt->shared_blocks = num_cpu * 2;
238 if (opt->shared_blocks < num_cpu || (uint64_t)opt->shared_blocks > max_blocks) {
239 ODPH_ERR(
"Invalid shared block count %u. Valid range is %u ... %" PRIu64
".\n",
240 opt->shared_blocks, num_cpu, max_blocks);
246 block_len = opt->data_len / opt->shared_blocks;
247 block_len &= ~((uint64_t)ODP_CACHE_LINE_SIZE - 1);
249 if (block_len < MIN_BLOCK_LEN) {
250 ODPH_ERR(
"Block size %" PRIu64
" is below %u bytes after cache line alignment.\n",
251 block_len, MIN_BLOCK_LEN);
255 global->block_len = block_len;
260static int create_shm(test_global_t *global)
266 test_options_t *test_options = &global->test_options;
267 uint32_t num_round = test_options->num_round;
268 uint32_t num_cpu = test_options->num_cpu;
269 uint64_t data_len = test_options->data_len;
270 uint32_t shm_flags = test_options->shm_flags;
271 int private = test_options->private;
272 char name[] =
"mem_perf_00";
278 printf(
"\nMemory performance test\n");
279 printf(
" num cpu %u\n", num_cpu);
280 printf(
" num rounds %u\n", num_round);
281 printf(
" data len %" PRIu64
"\n", data_len);
282 printf(
" memory footprint %" PRIu64
"\n", num_shm * data_len);
283 printf(
" shm flags 0x%x\n", shm_flags);
284 printf(
" num shm %u\n", num_shm);
285 printf(
" private %i\n",
private);
287 printf(
" shared blocks %u\n", test_options->shared_blocks);
288 printf(
" block len %" PRIu64
"\n", global->block_len);
290 printf(
" mode %i\n", test_options->mode);
293 ODPH_ERR(
"SHM capa failed.\n");
298 ODPH_ERR(
"Data len too large. Maximum len is %" PRIu64
"\n", shm_capa.
max_size);
303 ODPH_ERR(
"Too many SHM blocks. Maximum is %u\n", shm_capa.
max_blocks);
307 for (i = 0; i < num_shm; i++) {
308 name[9] =
'0' + i / 10;
309 name[10] =
'0' + i % 10;
314 ODPH_ERR(
"SHM[%u] reserve failed.\n", i);
318 global->shm[i] = shm;
322 ODPH_ERR(
"SHM[%u] addr failed.\n", i);
326 global->shm_addr[i] = addr;
328 printf(
" shm addr[%u] %p\n", i, addr);
332 global->num_shm = num_shm;
339static int free_shm(test_global_t *global)
343 for (i = 0; i < global->num_shm; i++) {
345 ODPH_ERR(
"SHM[%u] free failed.\n", i);
353static int create_stash(test_global_t *global)
357 test_options_t *opt = &global->test_options;
358 uint32_t num_block = opt->shared_blocks;
359 uint64_t block_len = global->block_len;
368 ODPH_ERR(
"FIFO stash is not supported.\n");
373 ODPH_ERR(
"Too many shared blocks for stash (%u). Maximum %" PRIu64
".\n",
391 ODPH_ERR(
"Stash create failed.\n");
395 base = global->shm_addr[0];
397 for (i = 0; i < num_block; i++) {
398 ptr = (uintptr_t)(base + ((uint64_t)i * block_len));
401 ODPH_ERR(
"Stash put failed for block %u.\n", i);
409static int destroy_stash(test_global_t *global)
415 if (global->test_options.private)
418 for (i = 0; i < global->test_options.shared_blocks; i++) {
421 ODPH_ERR(
"Stash get failed while emptying (%u).\n", i);
427 ODPH_ERR(
"Stash destroy failed.\n");
436static inline uint64_t read_data(
const uint64_t *data, uint64_t num_word)
443 uint64_t num_unroll = num_word & ~(uint64_t)0x7;
446 for (i = 0; i < num_unroll; i += 8) {
447 sum_0 += data[i + 0] + data[i + 1];
448 sum_1 += data[i + 2] + data[i + 3];
449 sum_2 += data[i + 4] + data[i + 5];
450 sum_3 += data[i + 6] + data[i + 7];
454 for (; i < num_word; i++)
457 return sum_0 + sum_1 + sum_2 + sum_3;
460static int run_test_private(
void *arg)
466 test_thread_ctx_t *thread_ctx = arg;
467 test_global_t *global = thread_ctx->global;
468 test_options_t *test_options = &global->test_options;
469 uint32_t num_round = test_options->num_round;
470 uint64_t data_len = test_options->data_len;
471 uint64_t half_len = data_len / 2;
472 uint64_t num_word = data_len /
sizeof(uint64_t);
473 uint64_t dummy_sum = 0;
474 int mode = test_options->mode;
475 uint8_t *addr = thread_ctx->shm_addr;
480 memset(addr, thr, data_len);
488 for (i = 0; i < num_round; i++)
489 memset(addr, thr + i, data_len);
490 }
else if (mode == 1) {
491 for (i = 0; i < num_round; i++)
492 memset(addr, 0, data_len);
493 }
else if (mode == 2) {
494 for (i = 0; i < num_round; i++) {
496 memcpy(&addr[half_len], addr, half_len);
498 memcpy(addr, &addr[half_len], half_len);
500 }
else if (mode == 3) {
501 for (i = 0; i < num_round; i++)
502 dummy_sum += read_data((
const uint64_t *)(uintptr_t)addr, num_word);
505 uint8_t *
volatile cmp_addr = addr;
507 for (i = 0; i < num_round; i++)
508 dummy_sum |= memcmp(cmp_addr, &cmp_addr[half_len], half_len);
516 thread_ctx->nsec = nsec;
517 thread_ctx->dummy_sum = dummy_sum;
522static inline int get_block(
odp_stash_t stash, uint8_t **addr)
526 int retry = MAX_STASH_RETRY;
533 ODPH_ERR(
"Stash get failed.\n");
539 ODPH_ERR(
"Stash get exceeded %d retries.\n", MAX_STASH_RETRY);
543 *addr = (uint8_t *)ptr;
547static inline int put_block(
odp_stash_t stash, uint8_t *addr)
549 uintptr_t ptr = (uintptr_t)addr;
551 int retry = MAX_STASH_RETRY;
558 ODPH_ERR(
"Stash put failed.\n");
564 ODPH_ERR(
"Stash put exceeded %d retries.\n", MAX_STASH_RETRY);
571static int run_test_shared(
void *arg)
577 test_thread_ctx_t *thread_ctx = arg;
578 test_global_t *global = thread_ctx->global;
579 test_options_t *test_options = &global->test_options;
580 uint32_t num_round = test_options->num_round;
581 uint64_t block_len = global->block_len;
582 uint64_t area_len = (uint64_t)test_options->shared_blocks * block_len;
583 uint64_t half_len = block_len / 2;
584 uint64_t num_word = block_len /
sizeof(uint64_t);
585 uint64_t dummy_sum = 0;
586 const int mode = test_options->mode;
588 uint8_t *base = thread_ctx->shm_addr;
597 memset(base, 0, area_len);
599 memset(base, thr, area_len);
606 for (i = 0; i < num_round; i++) {
607 if (get_block(stash, &addr))
611 memset(addr, thr + i, block_len);
612 }
else if (mode == 1) {
613 memset(addr, 0, block_len);
614 }
else if (mode == 2) {
616 memcpy(&addr[half_len], addr, half_len);
618 memcpy(addr, &addr[half_len], half_len);
619 }
else if (mode == 3) {
620 dummy_sum += read_data((
const uint64_t *)(uintptr_t)addr, num_word);
622 dummy_sum |= memcmp(addr, &addr[half_len], half_len);
625 if (put_block(stash, addr))
633 thread_ctx->nsec = nsec;
634 thread_ctx->dummy_sum = dummy_sum;
639static int start_workers(test_global_t *global,
odp_instance_t instance)
641 odph_thread_common_param_t param;
643 test_options_t *test_options = &global->test_options;
644 int num_cpu = test_options->num_cpu;
645 odph_thread_param_t thr_param[num_cpu];
647 odph_thread_common_param_init(¶m);
648 param.instance = instance;
649 param.cpumask = &global->cpumask;
651 for (i = 0; i < num_cpu; i++) {
652 test_thread_ctx_t *thread_ctx = &global->thread_ctx[i];
654 thread_ctx->global = global;
655 thread_ctx->shm_addr = global->shm_addr[0];
656 if (global->test_options.private)
657 thread_ctx->shm_addr = global->shm_addr[i];
659 odph_thread_param_init(&thr_param[i]);
661 thr_param[i].start = test_options->private ? run_test_private : run_test_shared;
662 thr_param[i].arg = thread_ctx;
665 ret = odph_thread_create(global->thread_tbl, ¶m, thr_param, num_cpu);
666 if (ret != num_cpu) {
667 ODPH_ERR(
"Failed to create all threads %i\n", ret);
674static int print_stat(test_global_t *global)
679 test_options_t *test_options = &global->test_options;
680 int num_cpu = test_options->num_cpu;
681 uint32_t num_round = test_options->num_round;
682 uint64_t round_len = test_options->private ? test_options->data_len : global->block_len;
683 uint64_t nsec_sum = 0;
684 uint64_t dummy_sum = 0;
685 int num_mismatch = 0;
689 nsec_sum += global->thread_ctx[i].nsec;
690 dummy_sum += global->thread_ctx[i].dummy_sum;
692 if (global->thread_ctx[i].dummy_sum)
697 printf(
"No results.\n");
701 data_touch = num_round * round_len;
702 nsec_ave = nsec_sum / num_cpu;
705 printf(
"\ndummy_sum: %" PRIu64
"\n\n", dummy_sum);
707 if (test_options->mode == 4 && num_mismatch) {
708 printf(
"WARNING: memcmp mismatch on %i/%i threads!\n\n", num_mismatch, num_cpu);
712 printf(
"RESULTS - per thread (MB per sec):\n");
713 printf(
"----------------------------------\n");
714 printf(
" 1 2 3 4 5 6 7 8 9 10");
717 if (global->thread_ctx[i].nsec) {
721 printf(
"%8.1f ", data_touch / (global->thread_ctx[i].nsec / 1000.0));
728 printf(
"WARNING: thread result miss-match %i/%i!\n\n", num, num_cpu);
730 printf(
"RESULTS - average over %i threads:\n", num_cpu);
731 printf(
"----------------------------------\n");
732 printf(
" duration: %.6f sec\n", nsec_ave / 1000000000);
733 printf(
" bandwidth per cpu: %.3f MB/s\n", data_touch / (nsec_ave / 1000.0));
734 printf(
" total bandwidth: %.3f MB/s\n", (num_cpu * data_touch) / (nsec_ave / 1000.0));
740int main(
int argc,
char **argv)
742 odph_helper_options_t helper_options;
746 test_global_t *global;
747 test_options_t test_options;
751 argc = odph_parse_options(argc, argv);
752 if (odph_options(&helper_options)) {
753 ODPH_ERR(
"Reading ODP helper options failed.\n");
757 if (parse_options(argc, argv, &test_options))
774 init.
mem_model = helper_options.mem_model;
778 ODPH_ERR(
"Global init failed.\n");
784 ODPH_ERR(
"Local init failed.\n");
788 shm =
odp_shm_reserve(
"mem_perf_global",
sizeof(test_global_t), ODP_CACHE_LINE_SIZE, 0);
790 ODPH_ERR(
"Shared mem reserve failed.\n");
795 if (global == NULL) {
796 ODPH_ERR(
"Shared mem alloc failed\n");
800 memset(global, 0,
sizeof(test_global_t));
801 global->test_options = test_options;
805 if (set_num_cpu(global))
808 if (set_shared_blocks(global))
811 if (create_shm(global))
814 if (create_stash(global))
818 if (start_workers(global, instance))
822 odph_thread_join(global->thread_tbl, global->test_options.num_cpu);
824 ret = print_stat(global);
826 if (destroy_stash(global))
829 if (free_shm(global))
833 ODPH_ERR(
"Shared mem free failed.\n");
838 ODPH_ERR(
"term local failed.\n");
843 ODPH_ERR(
"term global failed.\n");
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.
#define odp_unlikely(x)
Branch unlikely taken.
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.
void odp_shm_print_all(void)
Print all shared memory blocks.
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.
int odp_shm_capability(odp_shm_capability_t *capa)
Query shared memory capabilities.
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.
int32_t odp_stash_get_ptr(odp_stash_t stash, uintptr_t ptr[], int32_t num)
Get pointers from a stash.
void odp_stash_param_init(odp_stash_param_t *param)
Initialize stash params.
int odp_stash_destroy(odp_stash_t stash)
Destroy a stash.
int odp_stash_capability(odp_stash_capability_t *capa, odp_stash_type_t type)
Query stash capabilities.
int32_t odp_stash_put_ptr(odp_stash_t stash, const uintptr_t ptr[], int32_t num)
Put pointers into a stash.
odp_stash_t odp_stash_create(const char *name, const odp_stash_param_t *param)
Create a stash.
@ ODP_STASH_TYPE_FIFO
Stash type FIFO.
@ ODP_STASH_OP_MT
Multi-thread safe operation.
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_THREAD_CONTROL
Control 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.
Global initialization parameters.
odp_mem_model_t mem_model
Application memory model.
odp_feature_t not_used
Unused features.
Shared memory capabilities.
uint32_t max_blocks
Maximum number of shared memory blocks.
uint64_t max_size
Maximum memory block size in bytes.
Stash capabilities (per stash type)
uint64_t ptr
Maximum number of pointers.
uint32_t max_stashes
Maximum number of stashes of this type.
struct odp_stash_capability_t::@171 max_num
Maximum number of object handles per stash for each object size.
uint32_t cache_size
Maximum number of object handles cached locally per thread.
odp_stash_op_mode_t get_mode
Get operation mode.
odp_bool_t strict_size
Strict size.
uint32_t obj_size
Object handle size in bytes.
odp_stash_type_t type
Stash type.
odp_stash_op_mode_t put_mode
Put operation mode.
uint64_t num_obj
Number of object handles.
uint32_t tm
Traffic Manager APIs, e.g., odp_tm_xxx()
uint32_t stash
Stash APIs, e.g., odp_stash_xxx()
uint32_t crypto
Crypto APIs, e.g., odp_crypto_xxx()
uint32_t ipsec
IPsec APIs, e.g., odp_ipsec_xxx()
uint32_t dma
DMA APIs, e.g., odp_dma_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()
uint32_t ml
Machine Learning APIs, e.g., odp_ml_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()