Skip to content

Commit

Permalink
test: performance: add timed benchmark framework variant
Browse files Browse the repository at this point in the history
Add new benchmark framework variant for measuring function call latencies.
With the new framework latency measurements are performed within each test
case and a single test case may include multiple measured functions.

Signed-off-by: Matias Elo <matias.elo@nokia.com>
Reviewed-by: Petri Savolainen <petri.savolainen@nokia.com>
  • Loading branch information
MatiasElo committed Oct 6, 2023
1 parent 6f6488e commit ca64450
Show file tree
Hide file tree
Showing 2 changed files with 222 additions and 3 deletions.
107 changes: 107 additions & 0 deletions test/performance/bench_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,110 @@ int bench_run(void *arg)

return 0;
}

void bench_tm_suite_init(bench_tm_suite_t *suite)
{
memset(suite, 0, sizeof(bench_suite_t));

odp_atomic_init_u32(&suite->exit_worker, 0);
}

uint8_t bench_tm_func_register(bench_tm_result_t *res, const char *func_name)
{
uint8_t num_func = res->num;

if (num_func >= BENCH_TM_MAX_FUNC)
ODPH_ABORT("Too many test functions (max %d)\n", BENCH_TM_MAX_FUNC);

res->func[num_func].name = func_name;
res->num++;

return num_func;
}

void bench_tm_func_record(odp_time_t t2, odp_time_t t1, bench_tm_result_t *res, uint8_t id)
{
odp_time_t diff = odp_time_diff(t2, t1);

ODPH_ASSERT(id < BENCH_TM_MAX_FUNC);

res->func[id].tot = odp_time_sum(res->func[id].tot, diff);

if (odp_time_cmp(diff, res->func[id].min) < 0)
res->func[id].min = diff;

if (odp_time_cmp(diff, res->func[id].max) > 0)
res->func[id].max = diff;
}

static void init_result(bench_tm_result_t *res)
{
memset(res, 0, sizeof(bench_tm_result_t));

for (int i = 0; i < BENCH_TM_MAX_FUNC; i++) {
res->func[i].tot = ODP_TIME_NULL;
res->func[i].min = odp_time_local_from_ns(ODP_TIME_HOUR_IN_NS);
res->func[i].max = ODP_TIME_NULL;
}
}

static void print_results(bench_tm_result_t *res, uint64_t repeat_count)
{
for (uint8_t i = 0; i < res->num; i++) {
printf(" %-38s %-12" PRIu64 " %-12" PRIu64 " %-12" PRIu64 "\n",
res->func[i].name,
odp_time_to_ns(res->func[i].min),
odp_time_to_ns(res->func[i].tot) / repeat_count,
odp_time_to_ns(res->func[i].max));
}
}

int bench_tm_run(void *arg)
{
bench_tm_suite_t *suite = arg;

printf("\nLatency (nsec) per function call min avg max\n");
printf("------------------------------------------------------------------------------\n");

/* Run each test twice. Results from the first warm-up round are ignored. */
for (uint32_t i = 0; i < 2; i++) {
for (uint32_t j = 0; j < suite->num_bench; j++) {
const bench_tm_info_t *bench = &suite->bench[j];
uint64_t rounds = suite->rounds;
bench_tm_result_t res;

if (odp_atomic_load_u32(&suite->exit_worker))
return 0;

/* Run only selected test case */
if (suite->bench_idx && (j + 1) != suite->bench_idx)
continue;

if (bench->max_rounds && bench->max_rounds < rounds)
rounds = bench->max_rounds;

init_result(&res);

if (bench->init != NULL)
bench->init();

if (bench->run(&res, rounds)) {
ODPH_ERR("Benchmark %s failed\n", bench->name);
suite->retval = -1;
return -1;
}

if (bench->term != NULL)
bench->term();

/* No print or results from warm-up round */
if (i > 0) {
printf("[%02d] %-26s\n", j + 1, bench->name);
print_results(&res, rounds);
}
}
}
printf("\n");

return 0;
}
118 changes: 115 additions & 3 deletions test/performance/bench_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ extern "C" {
/**
* Check benchmark preconditions
*
* @retval !0 test enabled
* Returns !0 if benchmark precondition is met.
*/
typedef int (*bench_cond_fn_t)(void);

Expand All @@ -29,7 +29,7 @@ typedef void (*bench_init_fn_t)(void);
/**
* Run benchmark
*
* @retval >0 on success
* Returns >0 on success.
*/
typedef int (*bench_run_fn_t)(void);

Expand Down Expand Up @@ -108,10 +108,122 @@ void bench_suite_init(bench_suite_t *suite);
void bench_run_indef(bench_info_t *info, odp_atomic_u32_t *exit_thread);

/**
* Run tests suite and print results
* Run test suite and print results
*
* The argument is of type 'bench_suite_t *'. Returns 0 on success and <0 on failure.
*/
int bench_run(void *arg);

/*
* Timed benchmark framework
*
* The main difference compared to the standard benchmark suite is that all
* latency measurements are performed inside the test cases.
*/

/* Maximum number of benchmarked functions per test case */
#define BENCH_TM_MAX_FUNC 8

/* Timed benchmark results */
typedef struct bench_tm_results_s {
/* Results per function */
struct {
/* Name of function */
const char *name;

/* Total duration of all function calls */
odp_time_t tot;

/* Minimum duration */
odp_time_t min;

/* Maximum duration */
odp_time_t max;

} func[BENCH_TM_MAX_FUNC];

/* Number of registered test functions */
uint8_t num;

} bench_tm_result_t;

/**
* Timed benchmark test case
*
* Returns 0 on success and <0 on failure.
*/
typedef int (*bench_tm_run_fn_t)(bench_tm_result_t *res, int repeat_count);

/* Timed benchmark test case */
typedef struct {
/* Test case name */
const char *name;

/* Optional test initializer function */
bench_init_fn_t init;

/* Test function to run */
bench_tm_run_fn_t run;

/* Optional test termination function */
bench_term_fn_t term;

/* Optional test specific limit for rounds (tuning for slow implementations) */
uint32_t max_rounds;

} bench_tm_info_t;

/* Timed benchmark suite data */
typedef struct {
/* Array of benchmark test cases */
bench_tm_info_t *bench;

/* Number of benchmark test cases */
uint32_t num_bench;

/* Optional benchmark index to run (1...num_bench) */
uint32_t bench_idx;

/* Suite exit value output */
int retval;

/* Number of rounds per test case */
uint64_t rounds;

/* Break worker loop if set to 1 */
odp_atomic_u32_t exit_worker;

} bench_tm_suite_t;

/**
* Initialize benchmark suite data
*/
void bench_tm_suite_init(bench_tm_suite_t *suite);

/**
* Register function for benchmarking
*
* Called by each test case to register benchmarked functions. Returns function
* ID for recording benchmark results. At most BENCH_TM_MAX_FUNC functions can
* be registered per test case.
*/
uint8_t bench_tm_func_register(bench_tm_result_t *res, const char *func_name);

/**
* Record results for previously registered function
*
* Test case must call this function every test round for each registered
* function.
*/
void bench_tm_func_record(odp_time_t t2, odp_time_t t1, bench_tm_result_t *res, uint8_t id);

/**
* Run timed test suite and print results
*
* The argument is of type 'bench_tm_suite_t *'. Returns 0 on success and <0 on failure.
*/
int bench_tm_run(void *arg);

#ifdef __cplusplus
}
#endif
Expand Down

0 comments on commit ca64450

Please sign in to comment.