]> git.proxmox.com Git - mirror_qemu.git/blob - tests/fp/fp-bench.c
fw_cfg: Fix -boot bootsplash error checking
[mirror_qemu.git] / tests / fp / fp-bench.c
1 /*
2 * fp-bench.c - A collection of simple floating point microbenchmarks.
3 *
4 * Copyright (C) 2018, Emilio G. Cota <cota@braap.org>
5 *
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
12
13 #include "qemu/osdep.h"
14 #include <math.h>
15 #include <fenv.h>
16 #include "qemu/timer.h"
17 #include "fpu/softfloat.h"
18
19 /* amortize the computation of random inputs */
20 #define OPS_PER_ITER 50000
21
22 #define MAX_OPERANDS 3
23
24 #define SEED_A 0xdeadfacedeadface
25 #define SEED_B 0xbadc0feebadc0fee
26 #define SEED_C 0xbeefdeadbeefdead
27
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,
37 };
38
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,
48 };
49
50 enum precision {
51 PREC_SINGLE,
52 PREC_DOUBLE,
53 PREC_FLOAT32,
54 PREC_FLOAT64,
55 PREC_MAX_NR,
56 };
57
58 enum rounding {
59 ROUND_EVEN,
60 ROUND_ZERO,
61 ROUND_DOWN,
62 ROUND_UP,
63 ROUND_TIEAWAY,
64 N_ROUND_MODES,
65 };
66
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",
73 };
74
75 enum tester {
76 TESTER_SOFT,
77 TESTER_HOST,
78 TESTER_MAX_NR,
79 };
80
81 static const char * const tester_names[] = {
82 [TESTER_SOFT] = "soft",
83 [TESTER_HOST] = "host",
84 [TESTER_MAX_NR] = NULL,
85 };
86
87 union fp {
88 float f;
89 double d;
90 float32 f32;
91 float64 f64;
92 uint64_t u64;
93 };
94
95 struct op_state;
96
97 typedef float (*float_func_t)(const struct op_state *s);
98 typedef double (*double_func_t)(const struct op_state *s);
99
100 union fp_func {
101 float_func_t float_func;
102 double_func_t double_func;
103 };
104
105 typedef void (*bench_func_t)(void);
106
107 struct op_desc {
108 const char * const name;
109 };
110
111 #define DEFAULT_DURATION_SECS 1
112
113 static uint64_t random_ops[MAX_OPERANDS] = {
114 SEED_A, SEED_B, SEED_C,
115 };
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;
125
126 /*
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).
130 */
131 static uint64_t xorshift64star(uint64_t x)
132 {
133 x ^= x >> 12; /* a */
134 x ^= x << 25; /* b */
135 x ^= x >> 27; /* c */
136 return x * UINT64_C(2685821657736338717);
137 }
138
139 static void update_random_ops(int n_ops, enum precision prec)
140 {
141 int i;
142
143 for (i = 0; i < n_ops; i++) {
144 uint64_t r = random_ops[i];
145
146 if (prec == PREC_SINGLE || PREC_FLOAT32) {
147 do {
148 r = xorshift64star(r);
149 } while (!float32_is_normal(r));
150 } else if (prec == PREC_DOUBLE || PREC_FLOAT64) {
151 do {
152 r = xorshift64star(r);
153 } while (!float64_is_normal(r));
154 } else {
155 g_assert_not_reached();
156 }
157 random_ops[i] = r;
158 }
159 }
160
161 static void fill_random(union fp *ops, int n_ops, enum precision prec,
162 bool no_neg)
163 {
164 int i;
165
166 for (i = 0; i < n_ops; i++) {
167 switch (prec) {
168 case PREC_SINGLE:
169 case PREC_FLOAT32:
170 ops[i].f32 = make_float32(random_ops[i]);
171 if (no_neg && float32_is_neg(ops[i].f32)) {
172 ops[i].f32 = float32_chs(ops[i].f32);
173 }
174 /* raise the exponent to limit the frequency of denormal results */
175 ops[i].f32 |= 0x40000000;
176 break;
177 case PREC_DOUBLE:
178 case PREC_FLOAT64:
179 ops[i].f64 = make_float64(random_ops[i]);
180 if (no_neg && float64_is_neg(ops[i].f64)) {
181 ops[i].f64 = float64_chs(ops[i].f64);
182 }
183 /* raise the exponent to limit the frequency of denormal results */
184 ops[i].f64 |= LIT64(0x4000000000000000);
185 break;
186 default:
187 g_assert_not_reached();
188 }
189 }
190 }
191
192 /*
193 * The main benchmark function. Instead of (ab)using macros, we rely
194 * on the compiler to unfold this at compile-time.
195 */
196 static void bench(enum precision prec, enum op op, int n_ops, bool no_neg)
197 {
198 int64_t tf = get_clock() + duration * 1000000000LL;
199
200 while (get_clock() < tf) {
201 union fp ops[MAX_OPERANDS];
202 int64_t t0;
203 int i;
204
205 update_random_ops(n_ops, prec);
206 switch (prec) {
207 case PREC_SINGLE:
208 fill_random(ops, n_ops, prec, no_neg);
209 t0 = get_clock();
210 for (i = 0; i < OPS_PER_ITER; i++) {
211 float a = ops[0].f;
212 float b = ops[1].f;
213 float c = ops[2].f;
214
215 switch (op) {
216 case OP_ADD:
217 res.f = a + b;
218 break;
219 case OP_SUB:
220 res.f = a - b;
221 break;
222 case OP_MUL:
223 res.f = a * b;
224 break;
225 case OP_DIV:
226 res.f = a / b;
227 break;
228 case OP_FMA:
229 res.f = fmaf(a, b, c);
230 break;
231 case OP_SQRT:
232 res.f = sqrtf(a);
233 break;
234 case OP_CMP:
235 res.u64 = isgreater(a, b);
236 break;
237 default:
238 g_assert_not_reached();
239 }
240 }
241 break;
242 case PREC_DOUBLE:
243 fill_random(ops, n_ops, prec, no_neg);
244 t0 = get_clock();
245 for (i = 0; i < OPS_PER_ITER; i++) {
246 double a = ops[0].d;
247 double b = ops[1].d;
248 double c = ops[2].d;
249
250 switch (op) {
251 case OP_ADD:
252 res.d = a + b;
253 break;
254 case OP_SUB:
255 res.d = a - b;
256 break;
257 case OP_MUL:
258 res.d = a * b;
259 break;
260 case OP_DIV:
261 res.d = a / b;
262 break;
263 case OP_FMA:
264 res.d = fma(a, b, c);
265 break;
266 case OP_SQRT:
267 res.d = sqrt(a);
268 break;
269 case OP_CMP:
270 res.u64 = isgreater(a, b);
271 break;
272 default:
273 g_assert_not_reached();
274 }
275 }
276 break;
277 case PREC_FLOAT32:
278 fill_random(ops, n_ops, prec, no_neg);
279 t0 = get_clock();
280 for (i = 0; i < OPS_PER_ITER; i++) {
281 float32 a = ops[0].f32;
282 float32 b = ops[1].f32;
283 float32 c = ops[2].f32;
284
285 switch (op) {
286 case OP_ADD:
287 res.f32 = float32_add(a, b, &soft_status);
288 break;
289 case OP_SUB:
290 res.f32 = float32_sub(a, b, &soft_status);
291 break;
292 case OP_MUL:
293 res.f = float32_mul(a, b, &soft_status);
294 break;
295 case OP_DIV:
296 res.f32 = float32_div(a, b, &soft_status);
297 break;
298 case OP_FMA:
299 res.f32 = float32_muladd(a, b, c, 0, &soft_status);
300 break;
301 case OP_SQRT:
302 res.f32 = float32_sqrt(a, &soft_status);
303 break;
304 case OP_CMP:
305 res.u64 = float32_compare_quiet(a, b, &soft_status);
306 break;
307 default:
308 g_assert_not_reached();
309 }
310 }
311 break;
312 case PREC_FLOAT64:
313 fill_random(ops, n_ops, prec, no_neg);
314 t0 = get_clock();
315 for (i = 0; i < OPS_PER_ITER; i++) {
316 float64 a = ops[0].f64;
317 float64 b = ops[1].f64;
318 float64 c = ops[2].f64;
319
320 switch (op) {
321 case OP_ADD:
322 res.f64 = float64_add(a, b, &soft_status);
323 break;
324 case OP_SUB:
325 res.f64 = float64_sub(a, b, &soft_status);
326 break;
327 case OP_MUL:
328 res.f = float64_mul(a, b, &soft_status);
329 break;
330 case OP_DIV:
331 res.f64 = float64_div(a, b, &soft_status);
332 break;
333 case OP_FMA:
334 res.f64 = float64_muladd(a, b, c, 0, &soft_status);
335 break;
336 case OP_SQRT:
337 res.f64 = float64_sqrt(a, &soft_status);
338 break;
339 case OP_CMP:
340 res.u64 = float64_compare_quiet(a, b, &soft_status);
341 break;
342 default:
343 g_assert_not_reached();
344 }
345 }
346 break;
347 default:
348 g_assert_not_reached();
349 }
350 ns_elapsed += get_clock() - t0;
351 n_completed_ops += OPS_PER_ITER;
352 }
353 }
354
355 #define GEN_BENCH(name, type, prec, op, n_ops) \
356 static void __attribute__((flatten)) name(void) \
357 { \
358 bench(prec, op, n_ops, false); \
359 }
360
361 #define GEN_BENCH_NO_NEG(name, type, prec, op, n_ops) \
362 static void __attribute__((flatten)) name(void) \
363 { \
364 bench(prec, op, n_ops, true); \
365 }
366
367 #define GEN_BENCH_ALL_TYPES(opname, op, n_ops) \
368 GEN_BENCH(bench_ ## opname ## _float, float, PREC_SINGLE, op, n_ops) \
369 GEN_BENCH(bench_ ## opname ## _double, double, PREC_DOUBLE, op, n_ops) \
370 GEN_BENCH(bench_ ## opname ## _float32, float32, PREC_FLOAT32, op, n_ops) \
371 GEN_BENCH(bench_ ## opname ## _float64, float64, PREC_FLOAT64, op, n_ops)
372
373 GEN_BENCH_ALL_TYPES(add, OP_ADD, 2)
374 GEN_BENCH_ALL_TYPES(sub, OP_SUB, 2)
375 GEN_BENCH_ALL_TYPES(mul, OP_MUL, 2)
376 GEN_BENCH_ALL_TYPES(div, OP_DIV, 2)
377 GEN_BENCH_ALL_TYPES(fma, OP_FMA, 3)
378 GEN_BENCH_ALL_TYPES(cmp, OP_CMP, 2)
379 #undef GEN_BENCH_ALL_TYPES
380
381 #define GEN_BENCH_ALL_TYPES_NO_NEG(name, op, n) \
382 GEN_BENCH_NO_NEG(bench_ ## name ## _float, float, PREC_SINGLE, op, n) \
383 GEN_BENCH_NO_NEG(bench_ ## name ## _double, double, PREC_DOUBLE, op, n) \
384 GEN_BENCH_NO_NEG(bench_ ## name ## _float32, float32, PREC_FLOAT32, op, n) \
385 GEN_BENCH_NO_NEG(bench_ ## name ## _float64, float64, PREC_FLOAT64, op, n)
386
387 GEN_BENCH_ALL_TYPES_NO_NEG(sqrt, OP_SQRT, 1)
388 #undef GEN_BENCH_ALL_TYPES_NO_NEG
389
390 #undef GEN_BENCH_NO_NEG
391 #undef GEN_BENCH
392
393 #define GEN_BENCH_FUNCS(opname, op) \
394 [op] = { \
395 [PREC_SINGLE] = bench_ ## opname ## _float, \
396 [PREC_DOUBLE] = bench_ ## opname ## _double, \
397 [PREC_FLOAT32] = bench_ ## opname ## _float32, \
398 [PREC_FLOAT64] = bench_ ## opname ## _float64, \
399 }
400
401 static const bench_func_t bench_funcs[OP_MAX_NR][PREC_MAX_NR] = {
402 GEN_BENCH_FUNCS(add, OP_ADD),
403 GEN_BENCH_FUNCS(sub, OP_SUB),
404 GEN_BENCH_FUNCS(mul, OP_MUL),
405 GEN_BENCH_FUNCS(div, OP_DIV),
406 GEN_BENCH_FUNCS(fma, OP_FMA),
407 GEN_BENCH_FUNCS(sqrt, OP_SQRT),
408 GEN_BENCH_FUNCS(cmp, OP_CMP),
409 };
410
411 #undef GEN_BENCH_FUNCS
412
413 static void run_bench(void)
414 {
415 bench_func_t f;
416
417 f = bench_funcs[operation][precision];
418 g_assert(f);
419 f();
420 }
421
422 /* @arr must be NULL-terminated */
423 static int find_name(const char * const *arr, const char *name)
424 {
425 int i;
426
427 for (i = 0; arr[i] != NULL; i++) {
428 if (strcmp(name, arr[i]) == 0) {
429 return i;
430 }
431 }
432 return -1;
433 }
434
435 static void usage_complete(int argc, char *argv[])
436 {
437 gchar *op_list = g_strjoinv(", ", (gchar **)op_names);
438 gchar *tester_list = g_strjoinv(", ", (gchar **)tester_names);
439
440 fprintf(stderr, "Usage: %s [options]\n", argv[0]);
441 fprintf(stderr, "options:\n");
442 fprintf(stderr, " -d = duration, in seconds. Default: %d\n",
443 DEFAULT_DURATION_SECS);
444 fprintf(stderr, " -h = show this help message.\n");
445 fprintf(stderr, " -o = floating point operation (%s). Default: %s\n",
446 op_list, op_names[0]);
447 fprintf(stderr, " -p = floating point precision (single, double). "
448 "Default: single\n");
449 fprintf(stderr, " -r = rounding mode (even, zero, down, up, tieaway). "
450 "Default: even\n");
451 fprintf(stderr, " -t = tester (%s). Default: %s\n",
452 tester_list, tester_names[0]);
453 fprintf(stderr, " -z = flush inputs to zero (soft tester only). "
454 "Default: disabled\n");
455 fprintf(stderr, " -Z = flush output to zero (soft tester only). "
456 "Default: disabled\n");
457
458 g_free(tester_list);
459 g_free(op_list);
460 }
461
462 static int round_name_to_mode(const char *name)
463 {
464 int i;
465
466 for (i = 0; i < N_ROUND_MODES; i++) {
467 if (!strcmp(round_names[i], name)) {
468 return i;
469 }
470 }
471 return -1;
472 }
473
474 static void QEMU_NORETURN die_host_rounding(enum rounding rounding)
475 {
476 fprintf(stderr, "fatal: '%s' rounding not supported on this host\n",
477 round_names[rounding]);
478 exit(EXIT_FAILURE);
479 }
480
481 static void set_host_precision(enum rounding rounding)
482 {
483 int rhost;
484
485 switch (rounding) {
486 case ROUND_EVEN:
487 rhost = FE_TONEAREST;
488 break;
489 case ROUND_ZERO:
490 rhost = FE_TOWARDZERO;
491 break;
492 case ROUND_DOWN:
493 rhost = FE_DOWNWARD;
494 break;
495 case ROUND_UP:
496 rhost = FE_UPWARD;
497 break;
498 case ROUND_TIEAWAY:
499 die_host_rounding(rounding);
500 return;
501 default:
502 g_assert_not_reached();
503 }
504
505 if (fesetround(rhost)) {
506 die_host_rounding(rounding);
507 }
508 }
509
510 static void set_soft_precision(enum rounding rounding)
511 {
512 signed char mode;
513
514 switch (rounding) {
515 case ROUND_EVEN:
516 mode = float_round_nearest_even;
517 break;
518 case ROUND_ZERO:
519 mode = float_round_to_zero;
520 break;
521 case ROUND_DOWN:
522 mode = float_round_down;
523 break;
524 case ROUND_UP:
525 mode = float_round_up;
526 break;
527 case ROUND_TIEAWAY:
528 mode = float_round_ties_away;
529 break;
530 default:
531 g_assert_not_reached();
532 }
533 soft_status.float_rounding_mode = mode;
534 }
535
536 static void parse_args(int argc, char *argv[])
537 {
538 int c;
539 int val;
540 int rounding = ROUND_EVEN;
541
542 for (;;) {
543 c = getopt(argc, argv, "d:ho:p:r:t:zZ");
544 if (c < 0) {
545 break;
546 }
547 switch (c) {
548 case 'd':
549 duration = atoi(optarg);
550 break;
551 case 'h':
552 usage_complete(argc, argv);
553 exit(EXIT_SUCCESS);
554 case 'o':
555 val = find_name(op_names, optarg);
556 if (val < 0) {
557 fprintf(stderr, "Unsupported op '%s'\n", optarg);
558 exit(EXIT_FAILURE);
559 }
560 operation = val;
561 break;
562 case 'p':
563 if (!strcmp(optarg, "single")) {
564 precision = PREC_SINGLE;
565 } else if (!strcmp(optarg, "double")) {
566 precision = PREC_DOUBLE;
567 } else {
568 fprintf(stderr, "Unsupported precision '%s'\n", optarg);
569 exit(EXIT_FAILURE);
570 }
571 break;
572 case 'r':
573 rounding = round_name_to_mode(optarg);
574 if (rounding < 0) {
575 fprintf(stderr, "fatal: invalid rounding mode '%s'\n", optarg);
576 exit(EXIT_FAILURE);
577 }
578 break;
579 case 't':
580 val = find_name(tester_names, optarg);
581 if (val < 0) {
582 fprintf(stderr, "Unsupported tester '%s'\n", optarg);
583 exit(EXIT_FAILURE);
584 }
585 tester = val;
586 break;
587 case 'z':
588 soft_status.flush_inputs_to_zero = 1;
589 break;
590 case 'Z':
591 soft_status.flush_to_zero = 1;
592 break;
593 }
594 }
595
596 /* set precision and rounding mode based on the tester */
597 switch (tester) {
598 case TESTER_HOST:
599 set_host_precision(rounding);
600 break;
601 case TESTER_SOFT:
602 set_soft_precision(rounding);
603 switch (precision) {
604 case PREC_SINGLE:
605 precision = PREC_FLOAT32;
606 break;
607 case PREC_DOUBLE:
608 precision = PREC_FLOAT64;
609 break;
610 default:
611 g_assert_not_reached();
612 }
613 break;
614 default:
615 g_assert_not_reached();
616 }
617 }
618
619 static void pr_stats(void)
620 {
621 printf("%.2f MFlops\n", (double)n_completed_ops / ns_elapsed * 1e3);
622 }
623
624 int main(int argc, char *argv[])
625 {
626 parse_args(argc, argv);
627 run_bench();
628 pr_stats();
629 return 0;
630 }