Merge remote-tracking branch 'remotes/ericb/tags/pull-nbd-2019-07-19' into staging
[qemu/ar7.git] / tests / fp / fp-bench.c
blob4ba5e1d2d4d321a4c9b3ee70dca2721c9561ddb7
1 /*
2 * fp-bench.c - A collection of simple floating point microbenchmarks.
4 * Copyright (C) 2018, Emilio G. Cota <cota@braap.org>
6 * License: GNU GPL, version 2 or later.
7 * See the COPYING file in the top-level directory.
8 */
9 #ifndef HW_POISON_H
10 #error Must define HW_POISON_H to work around TARGET_* poisoning
11 #endif
13 #include "qemu/osdep.h"
14 #include <math.h>
15 #include <fenv.h>
16 #include "qemu/timer.h"
17 #include "fpu/softfloat.h"
19 /* amortize the computation of random inputs */
20 #define OPS_PER_ITER 50000
22 #define MAX_OPERANDS 3
24 #define SEED_A 0xdeadfacedeadface
25 #define SEED_B 0xbadc0feebadc0fee
26 #define SEED_C 0xbeefdeadbeefdead
28 enum op {
29 OP_ADD,
30 OP_SUB,
31 OP_MUL,
32 OP_DIV,
33 OP_FMA,
34 OP_SQRT,
35 OP_CMP,
36 OP_MAX_NR,
39 static const char * const op_names[] = {
40 [OP_ADD] = "add",
41 [OP_SUB] = "sub",
42 [OP_MUL] = "mul",
43 [OP_DIV] = "div",
44 [OP_FMA] = "mulAdd",
45 [OP_SQRT] = "sqrt",
46 [OP_CMP] = "cmp",
47 [OP_MAX_NR] = NULL,
50 enum precision {
51 PREC_SINGLE,
52 PREC_DOUBLE,
53 PREC_FLOAT32,
54 PREC_FLOAT64,
55 PREC_MAX_NR,
58 enum rounding {
59 ROUND_EVEN,
60 ROUND_ZERO,
61 ROUND_DOWN,
62 ROUND_UP,
63 ROUND_TIEAWAY,
64 N_ROUND_MODES,
67 static const char * const round_names[] = {
68 [ROUND_EVEN] = "even",
69 [ROUND_ZERO] = "zero",
70 [ROUND_DOWN] = "down",
71 [ROUND_UP] = "up",
72 [ROUND_TIEAWAY] = "tieaway",
75 enum tester {
76 TESTER_SOFT,
77 TESTER_HOST,
78 TESTER_MAX_NR,
81 static const char * const tester_names[] = {
82 [TESTER_SOFT] = "soft",
83 [TESTER_HOST] = "host",
84 [TESTER_MAX_NR] = NULL,
87 union fp {
88 float f;
89 double d;
90 float32 f32;
91 float64 f64;
92 uint64_t u64;
95 struct op_state;
97 typedef float (*float_func_t)(const struct op_state *s);
98 typedef double (*double_func_t)(const struct op_state *s);
100 union fp_func {
101 float_func_t float_func;
102 double_func_t double_func;
105 typedef void (*bench_func_t)(void);
107 struct op_desc {
108 const char * const name;
111 #define DEFAULT_DURATION_SECS 1
113 static uint64_t random_ops[MAX_OPERANDS] = {
114 SEED_A, SEED_B, SEED_C,
116 static float_status soft_status;
117 static enum precision precision;
118 static enum op operation;
119 static enum tester tester;
120 static uint64_t n_completed_ops;
121 static unsigned int duration = DEFAULT_DURATION_SECS;
122 static int64_t ns_elapsed;
123 /* disable optimizations with volatile */
124 static volatile union fp res;
127 * From: https://en.wikipedia.org/wiki/Xorshift
128 * This is faster than rand_r(), and gives us a wider range (RAND_MAX is only
129 * guaranteed to be >= INT_MAX).
131 static uint64_t xorshift64star(uint64_t x)
133 x ^= x >> 12; /* a */
134 x ^= x << 25; /* b */
135 x ^= x >> 27; /* c */
136 return x * UINT64_C(2685821657736338717);
139 static void update_random_ops(int n_ops, enum precision prec)
141 int i;
143 for (i = 0; i < n_ops; i++) {
144 uint64_t r = random_ops[i];
146 switch (prec) {
147 case PREC_SINGLE:
148 case PREC_FLOAT32:
149 do {
150 r = xorshift64star(r);
151 } while (!float32_is_normal(r));
152 break;
153 case PREC_DOUBLE:
154 case PREC_FLOAT64:
155 do {
156 r = xorshift64star(r);
157 } while (!float64_is_normal(r));
158 break;
159 default:
160 g_assert_not_reached();
162 random_ops[i] = r;
166 static void fill_random(union fp *ops, int n_ops, enum precision prec,
167 bool no_neg)
169 int i;
171 for (i = 0; i < n_ops; i++) {
172 switch (prec) {
173 case PREC_SINGLE:
174 case PREC_FLOAT32:
175 ops[i].f32 = make_float32(random_ops[i]);
176 if (no_neg && float32_is_neg(ops[i].f32)) {
177 ops[i].f32 = float32_chs(ops[i].f32);
179 break;
180 case PREC_DOUBLE:
181 case PREC_FLOAT64:
182 ops[i].f64 = make_float64(random_ops[i]);
183 if (no_neg && float64_is_neg(ops[i].f64)) {
184 ops[i].f64 = float64_chs(ops[i].f64);
186 break;
187 default:
188 g_assert_not_reached();
194 * The main benchmark function. Instead of (ab)using macros, we rely
195 * on the compiler to unfold this at compile-time.
197 static void bench(enum precision prec, enum op op, int n_ops, bool no_neg)
199 int64_t tf = get_clock() + duration * 1000000000LL;
201 while (get_clock() < tf) {
202 union fp ops[MAX_OPERANDS];
203 int64_t t0;
204 int i;
206 update_random_ops(n_ops, prec);
207 switch (prec) {
208 case PREC_SINGLE:
209 fill_random(ops, n_ops, prec, no_neg);
210 t0 = get_clock();
211 for (i = 0; i < OPS_PER_ITER; i++) {
212 float a = ops[0].f;
213 float b = ops[1].f;
214 float c = ops[2].f;
216 switch (op) {
217 case OP_ADD:
218 res.f = a + b;
219 break;
220 case OP_SUB:
221 res.f = a - b;
222 break;
223 case OP_MUL:
224 res.f = a * b;
225 break;
226 case OP_DIV:
227 res.f = a / b;
228 break;
229 case OP_FMA:
230 res.f = fmaf(a, b, c);
231 break;
232 case OP_SQRT:
233 res.f = sqrtf(a);
234 break;
235 case OP_CMP:
236 res.u64 = isgreater(a, b);
237 break;
238 default:
239 g_assert_not_reached();
242 break;
243 case PREC_DOUBLE:
244 fill_random(ops, n_ops, prec, no_neg);
245 t0 = get_clock();
246 for (i = 0; i < OPS_PER_ITER; i++) {
247 double a = ops[0].d;
248 double b = ops[1].d;
249 double c = ops[2].d;
251 switch (op) {
252 case OP_ADD:
253 res.d = a + b;
254 break;
255 case OP_SUB:
256 res.d = a - b;
257 break;
258 case OP_MUL:
259 res.d = a * b;
260 break;
261 case OP_DIV:
262 res.d = a / b;
263 break;
264 case OP_FMA:
265 res.d = fma(a, b, c);
266 break;
267 case OP_SQRT:
268 res.d = sqrt(a);
269 break;
270 case OP_CMP:
271 res.u64 = isgreater(a, b);
272 break;
273 default:
274 g_assert_not_reached();
277 break;
278 case PREC_FLOAT32:
279 fill_random(ops, n_ops, prec, no_neg);
280 t0 = get_clock();
281 for (i = 0; i < OPS_PER_ITER; i++) {
282 float32 a = ops[0].f32;
283 float32 b = ops[1].f32;
284 float32 c = ops[2].f32;
286 switch (op) {
287 case OP_ADD:
288 res.f32 = float32_add(a, b, &soft_status);
289 break;
290 case OP_SUB:
291 res.f32 = float32_sub(a, b, &soft_status);
292 break;
293 case OP_MUL:
294 res.f = float32_mul(a, b, &soft_status);
295 break;
296 case OP_DIV:
297 res.f32 = float32_div(a, b, &soft_status);
298 break;
299 case OP_FMA:
300 res.f32 = float32_muladd(a, b, c, 0, &soft_status);
301 break;
302 case OP_SQRT:
303 res.f32 = float32_sqrt(a, &soft_status);
304 break;
305 case OP_CMP:
306 res.u64 = float32_compare_quiet(a, b, &soft_status);
307 break;
308 default:
309 g_assert_not_reached();
312 break;
313 case PREC_FLOAT64:
314 fill_random(ops, n_ops, prec, no_neg);
315 t0 = get_clock();
316 for (i = 0; i < OPS_PER_ITER; i++) {
317 float64 a = ops[0].f64;
318 float64 b = ops[1].f64;
319 float64 c = ops[2].f64;
321 switch (op) {
322 case OP_ADD:
323 res.f64 = float64_add(a, b, &soft_status);
324 break;
325 case OP_SUB:
326 res.f64 = float64_sub(a, b, &soft_status);
327 break;
328 case OP_MUL:
329 res.f = float64_mul(a, b, &soft_status);
330 break;
331 case OP_DIV:
332 res.f64 = float64_div(a, b, &soft_status);
333 break;
334 case OP_FMA:
335 res.f64 = float64_muladd(a, b, c, 0, &soft_status);
336 break;
337 case OP_SQRT:
338 res.f64 = float64_sqrt(a, &soft_status);
339 break;
340 case OP_CMP:
341 res.u64 = float64_compare_quiet(a, b, &soft_status);
342 break;
343 default:
344 g_assert_not_reached();
347 break;
348 default:
349 g_assert_not_reached();
351 ns_elapsed += get_clock() - t0;
352 n_completed_ops += OPS_PER_ITER;
356 #define GEN_BENCH(name, type, prec, op, n_ops) \
357 static void __attribute__((flatten)) name(void) \
359 bench(prec, op, n_ops, false); \
362 #define GEN_BENCH_NO_NEG(name, type, prec, op, n_ops) \
363 static void __attribute__((flatten)) name(void) \
365 bench(prec, op, n_ops, true); \
368 #define GEN_BENCH_ALL_TYPES(opname, op, n_ops) \
369 GEN_BENCH(bench_ ## opname ## _float, float, PREC_SINGLE, op, n_ops) \
370 GEN_BENCH(bench_ ## opname ## _double, double, PREC_DOUBLE, op, n_ops) \
371 GEN_BENCH(bench_ ## opname ## _float32, float32, PREC_FLOAT32, op, n_ops) \
372 GEN_BENCH(bench_ ## opname ## _float64, float64, PREC_FLOAT64, op, n_ops)
374 GEN_BENCH_ALL_TYPES(add, OP_ADD, 2)
375 GEN_BENCH_ALL_TYPES(sub, OP_SUB, 2)
376 GEN_BENCH_ALL_TYPES(mul, OP_MUL, 2)
377 GEN_BENCH_ALL_TYPES(div, OP_DIV, 2)
378 GEN_BENCH_ALL_TYPES(fma, OP_FMA, 3)
379 GEN_BENCH_ALL_TYPES(cmp, OP_CMP, 2)
380 #undef GEN_BENCH_ALL_TYPES
382 #define GEN_BENCH_ALL_TYPES_NO_NEG(name, op, n) \
383 GEN_BENCH_NO_NEG(bench_ ## name ## _float, float, PREC_SINGLE, op, n) \
384 GEN_BENCH_NO_NEG(bench_ ## name ## _double, double, PREC_DOUBLE, op, n) \
385 GEN_BENCH_NO_NEG(bench_ ## name ## _float32, float32, PREC_FLOAT32, op, n) \
386 GEN_BENCH_NO_NEG(bench_ ## name ## _float64, float64, PREC_FLOAT64, op, n)
388 GEN_BENCH_ALL_TYPES_NO_NEG(sqrt, OP_SQRT, 1)
389 #undef GEN_BENCH_ALL_TYPES_NO_NEG
391 #undef GEN_BENCH_NO_NEG
392 #undef GEN_BENCH
394 #define GEN_BENCH_FUNCS(opname, op) \
395 [op] = { \
396 [PREC_SINGLE] = bench_ ## opname ## _float, \
397 [PREC_DOUBLE] = bench_ ## opname ## _double, \
398 [PREC_FLOAT32] = bench_ ## opname ## _float32, \
399 [PREC_FLOAT64] = bench_ ## opname ## _float64, \
402 static const bench_func_t bench_funcs[OP_MAX_NR][PREC_MAX_NR] = {
403 GEN_BENCH_FUNCS(add, OP_ADD),
404 GEN_BENCH_FUNCS(sub, OP_SUB),
405 GEN_BENCH_FUNCS(mul, OP_MUL),
406 GEN_BENCH_FUNCS(div, OP_DIV),
407 GEN_BENCH_FUNCS(fma, OP_FMA),
408 GEN_BENCH_FUNCS(sqrt, OP_SQRT),
409 GEN_BENCH_FUNCS(cmp, OP_CMP),
412 #undef GEN_BENCH_FUNCS
414 static void run_bench(void)
416 bench_func_t f;
418 f = bench_funcs[operation][precision];
419 g_assert(f);
420 f();
423 /* @arr must be NULL-terminated */
424 static int find_name(const char * const *arr, const char *name)
426 int i;
428 for (i = 0; arr[i] != NULL; i++) {
429 if (strcmp(name, arr[i]) == 0) {
430 return i;
433 return -1;
436 static void usage_complete(int argc, char *argv[])
438 gchar *op_list = g_strjoinv(", ", (gchar **)op_names);
439 gchar *tester_list = g_strjoinv(", ", (gchar **)tester_names);
441 fprintf(stderr, "Usage: %s [options]\n", argv[0]);
442 fprintf(stderr, "options:\n");
443 fprintf(stderr, " -d = duration, in seconds. Default: %d\n",
444 DEFAULT_DURATION_SECS);
445 fprintf(stderr, " -h = show this help message.\n");
446 fprintf(stderr, " -o = floating point operation (%s). Default: %s\n",
447 op_list, op_names[0]);
448 fprintf(stderr, " -p = floating point precision (single, double). "
449 "Default: single\n");
450 fprintf(stderr, " -r = rounding mode (even, zero, down, up, tieaway). "
451 "Default: even\n");
452 fprintf(stderr, " -t = tester (%s). Default: %s\n",
453 tester_list, tester_names[0]);
454 fprintf(stderr, " -z = flush inputs to zero (soft tester only). "
455 "Default: disabled\n");
456 fprintf(stderr, " -Z = flush output to zero (soft tester only). "
457 "Default: disabled\n");
459 g_free(tester_list);
460 g_free(op_list);
463 static int round_name_to_mode(const char *name)
465 int i;
467 for (i = 0; i < N_ROUND_MODES; i++) {
468 if (!strcmp(round_names[i], name)) {
469 return i;
472 return -1;
475 static void QEMU_NORETURN die_host_rounding(enum rounding rounding)
477 fprintf(stderr, "fatal: '%s' rounding not supported on this host\n",
478 round_names[rounding]);
479 exit(EXIT_FAILURE);
482 static void set_host_precision(enum rounding rounding)
484 int rhost;
486 switch (rounding) {
487 case ROUND_EVEN:
488 rhost = FE_TONEAREST;
489 break;
490 case ROUND_ZERO:
491 rhost = FE_TOWARDZERO;
492 break;
493 case ROUND_DOWN:
494 rhost = FE_DOWNWARD;
495 break;
496 case ROUND_UP:
497 rhost = FE_UPWARD;
498 break;
499 case ROUND_TIEAWAY:
500 die_host_rounding(rounding);
501 return;
502 default:
503 g_assert_not_reached();
506 if (fesetround(rhost)) {
507 die_host_rounding(rounding);
511 static void set_soft_precision(enum rounding rounding)
513 signed char mode;
515 switch (rounding) {
516 case ROUND_EVEN:
517 mode = float_round_nearest_even;
518 break;
519 case ROUND_ZERO:
520 mode = float_round_to_zero;
521 break;
522 case ROUND_DOWN:
523 mode = float_round_down;
524 break;
525 case ROUND_UP:
526 mode = float_round_up;
527 break;
528 case ROUND_TIEAWAY:
529 mode = float_round_ties_away;
530 break;
531 default:
532 g_assert_not_reached();
534 soft_status.float_rounding_mode = mode;
537 static void parse_args(int argc, char *argv[])
539 int c;
540 int val;
541 int rounding = ROUND_EVEN;
543 for (;;) {
544 c = getopt(argc, argv, "d:ho:p:r:t:zZ");
545 if (c < 0) {
546 break;
548 switch (c) {
549 case 'd':
550 duration = atoi(optarg);
551 break;
552 case 'h':
553 usage_complete(argc, argv);
554 exit(EXIT_SUCCESS);
555 case 'o':
556 val = find_name(op_names, optarg);
557 if (val < 0) {
558 fprintf(stderr, "Unsupported op '%s'\n", optarg);
559 exit(EXIT_FAILURE);
561 operation = val;
562 break;
563 case 'p':
564 if (!strcmp(optarg, "single")) {
565 precision = PREC_SINGLE;
566 } else if (!strcmp(optarg, "double")) {
567 precision = PREC_DOUBLE;
568 } else {
569 fprintf(stderr, "Unsupported precision '%s'\n", optarg);
570 exit(EXIT_FAILURE);
572 break;
573 case 'r':
574 rounding = round_name_to_mode(optarg);
575 if (rounding < 0) {
576 fprintf(stderr, "fatal: invalid rounding mode '%s'\n", optarg);
577 exit(EXIT_FAILURE);
579 break;
580 case 't':
581 val = find_name(tester_names, optarg);
582 if (val < 0) {
583 fprintf(stderr, "Unsupported tester '%s'\n", optarg);
584 exit(EXIT_FAILURE);
586 tester = val;
587 break;
588 case 'z':
589 soft_status.flush_inputs_to_zero = 1;
590 break;
591 case 'Z':
592 soft_status.flush_to_zero = 1;
593 break;
597 /* set precision and rounding mode based on the tester */
598 switch (tester) {
599 case TESTER_HOST:
600 set_host_precision(rounding);
601 break;
602 case TESTER_SOFT:
603 set_soft_precision(rounding);
604 switch (precision) {
605 case PREC_SINGLE:
606 precision = PREC_FLOAT32;
607 break;
608 case PREC_DOUBLE:
609 precision = PREC_FLOAT64;
610 break;
611 default:
612 g_assert_not_reached();
614 break;
615 default:
616 g_assert_not_reached();
620 static void pr_stats(void)
622 printf("%.2f MFlops\n", (double)n_completed_ops / ns_elapsed * 1e3);
625 int main(int argc, char *argv[])
627 parse_args(argc, argv);
628 run_bench();
629 pr_stats();
630 return 0;