librfn
An ad-hoc utility library
libbench.c
Go to the documentation of this file.
1 /*
2  * libbench.c
3  *
4  * Part of librfn (a general utility library from redfelineninja.org.uk)
5  *
6  * Copyright (C) 2015 Daniel Thompson <daniel@redfelineninja.org.uk>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU Lesser General Public License as published
10  * by the Free Software Foundation; either version 3 of the License, or
11  * (at your option) any later version.
12  */
13 
14 #include <assert.h>
15 #include <stdio.h>
16 #include <string.h>
17 
18 #include "librfn.h"
19 #include "libbench.h"
20 
21 /*
22  * Assuming time_now() ticks at 1MHz (microseconds) then a million cycles
23  * results in a results in picoseconds per cycle. This is great for
24  * benchmarking powerful multi-GHz desktop machines. If BENCHMARK_SLOW_MACHINE
25  * is set we reduce the number of cycles and the results will be in
26  * nanoseconds per cycle.
27  */
28 #ifdef BENCHMARK_SLOW_MACHINE
29 #define NUM_CYCLES 1000
30 #else
31 #define NUM_CYCLES 1000000
32 #endif
33 
34 static fibre_t *next_action;
35 
36 typedef struct {
37  uint32_t start_time;
38  uint32_t end_time;
39  unsigned int cycles;
40  unsigned int count;
42  fibre_t *friend;
44 
45 static int yield_fibre(fibre_t *fibre)
46 {
47  benchmark_fibre_t *bm = containerof(fibre, benchmark_fibre_t, fibre);
48 
49  PT_BEGIN_FIBRE(fibre);
50 
51  bm->start_time = time_now();
52  bm->count = 0;
53 
54  while (bm->count++ < bm->cycles)
55  PT_YIELD();
56 
57  bm->end_time = time_now();
58  fibre_run(next_action);
59  PT_END();
60 }
61 
62 static int run_fibre(fibre_t *fibre)
63 {
64  benchmark_fibre_t *bm = containerof(fibre, benchmark_fibre_t, fibre);
65 
66  PT_BEGIN_FIBRE(fibre);
67 
68  bm->start_time = time_now();
69  bm->count = 0;
70 
71  while (bm->count++ < bm->cycles) {
72  fibre_run(bm->friend);
73  PT_WAIT();
74  }
75 
76  /* if we are fibre[0] we need to poke fibre[1] one last time */
77  if (bm->friend > fibre)
78  fibre_run(bm->friend);
79 
80  bm->end_time = time_now();
81  fibre_run(next_action);
82  PT_END();
83 }
84 
85 static int atomic_run_fibre(fibre_t *fibre)
86 {
87  benchmark_fibre_t *bm = containerof(fibre, benchmark_fibre_t, fibre);
88 
89  PT_BEGIN_FIBRE(fibre);
90 
91  bm->start_time = time_now();
92  bm->count = 0;
93 
94  while (bm->count++ < bm->cycles) {
96  PT_WAIT();
97  }
98 
99  /* if we are fibre[0] we need to poke fibre[1] one last time */
100  if (bm->friend > fibre)
102 
103  bm->end_time = time_now();
104  fibre_run(next_action);
105  PT_END();
106 }
107 
108 static benchmark_fibre_t single_yield = {
109  .cycles = NUM_CYCLES,
110  .fibre = FIBRE_VAR_INIT(yield_fibre)
111 };
112 
113 static benchmark_fibre_t paired_yield[2] = {
114  {
115  .cycles = NUM_CYCLES/2,
116  .fibre = FIBRE_VAR_INIT(yield_fibre)
117  },
118  {
119  .cycles = NUM_CYCLES/2,
120  .fibre = FIBRE_VAR_INIT(yield_fibre)
121  }
122 };
123 
124 static benchmark_fibre_t simple_run[2] = {
125  {
126  .cycles = NUM_CYCLES/2,
127  .fibre = FIBRE_VAR_INIT(run_fibre),
128  .friend = &simple_run[1].fibre,
129  },
130  {
131  .cycles = NUM_CYCLES/2,
132  .fibre = FIBRE_VAR_INIT(run_fibre),
133  .friend = &simple_run[0].fibre,
134  },
135 };
136 
137 static benchmark_fibre_t atomic_run[2] = {
138  {
139  .cycles = NUM_CYCLES/2,
140  .fibre = FIBRE_VAR_INIT(atomic_run_fibre),
141  .friend = &atomic_run[1].fibre,
142  },
143  {
144  .cycles = NUM_CYCLES/2,
145  .fibre = FIBRE_VAR_INIT(atomic_run_fibre),
146  .friend = &atomic_run[0].fibre,
147  },
148 };
149 
150 
152 {
153  memset(results, 0, sizeof(*results));
154  for (int i=0; i<lengthof(results->stats); i++)
155  stats_init(&results->stats[i]);
156  results->wakeup = wakeup;
157 }
158 
160 {
161  /* next_action is a bit of a hack but since it is meaningless to run
162  * two benchmarks concurrently it is sufficient to unconditionally
163  * update this every time we run.
164  */
165  next_action = results->wakeup;
166 
167  PT_BEGIN(&results->pt);
168 
169  fibre_run(&single_yield.fibre);
170  PT_WAIT();
171  stats_add(&results->stats[BENCHMARK_SINGLE],
172  single_yield.end_time - single_yield.start_time);
173 
174  fibre_run(&paired_yield[0].fibre);
175  fibre_run(&paired_yield[1].fibre);
176  PT_WAIT();
177  stats_add(&results->stats[BENCHMARK_PAIRED],
178  paired_yield[1].end_time - paired_yield[0].start_time);
179 
180  fibre_run(&simple_run[0].fibre);
181  PT_WAIT();
183  simple_run[1].end_time - simple_run[0].start_time);
184 
185  fibre_run(&atomic_run[0].fibre);
186  PT_WAIT();
188  atomic_run[1].end_time - atomic_run[0].start_time);
189 
190  PT_END();
191 }
192 
193 static const char *lookup_name(int n)
194 {
195  static char lower[16];
196 #define C(x) case BENCHMARK_ ## x: strncpy(lower, #x, sizeof(lower)); break
197  switch (n) {
198  C(SINGLE);
199  C(PAIRED);
200  C(SIMPLE_RUN);
201  C(ATOMIC_RUN);
202  default:
203  return NULL;
204 #undef C
205  }
206 
207  lower[sizeof(lower)-1] = '\0';
208  (void) strtolower(lower);
209 
210  return lower;
211 }
212 
213 const char *benchmark_get_result(benchmark_results_t *results, int n,
214  stats_t *s)
215 {
216  const char *retval = lookup_name(n);
217  if (retval)
218  memcpy(s, &results->stats[n], sizeof(*s));
219 
220  return retval;
221 }
222 
224 {
225  const char *name;
226  stats_t result;
227 
228  printf("Test Min Mean Max\n");
229  printf("----------------------------------------------\n");
230 
231  for (int i = 0; (name = benchmark_get_result(results, i, &result)); i++)
232  printf("%-16s%11d%11d%11d\n", name,
233  result.min, stats_mean(&result), result.max);
234 }
235 
237 {
238  const char *name;
239  stats_t result;
240 
241  fprintf(f,"\"Test\",\"Min\",\"Mean\",\"Max\"\n");
242  for (int i = 0; (name = benchmark_get_result(results, i, &result)); i++)
243  fprintf(f, "\"%s\",%d,%d,%d\n", name,
244  result.min, stats_mean(&result), result.max);
245 }
#define containerof(ptr, type, member)
Definition: util.h:35
void stats_add(stats_t *s, statval_t d)
Definition: stats.c:30
void stats_init(stats_t *s)
Definition: stats.c:16
Micro benchmark suite for some librfn features.
unsigned int count
Definition: libbench.c:40
#define lengthof(x)
Definition: util.h:44
#define FIBRE_VAR_INIT(fn)
Static initializer for a fibre descriptor.
Definition: fibre.h:75
statval_t min
Definition: stats.h:42
int yield_fibre(fibre_t *f)
Definition: fibretest.c:49
statval_t max
Definition: stats.h:43
#define PT_BEGIN(pt)
Definition: protothreads.h:93
#define PT_YIELD()
Definition: protothreads.h:124
fibre_t * friend
Definition: libbench.c:42
int benchmark_run_once(benchmark_results_t *results)
Definition: libbench.c:159
#define PT_END()
Definition: protothreads.h:100
stats_t stats[BENCHMARK_MAX]
Definition: libbench.h:43
uint32_t end_time
Definition: libbench.c:38
uint32_t start_time
Definition: libbench.c:37
#define C(x)
fibre_t fibre
Definition: libbench.c:41
const char * benchmark_get_result(benchmark_results_t *results, int n, stats_t *s)
Definition: libbench.c:213
void benchmark_show_results(benchmark_results_t *results)
Definition: libbench.c:223
#define PT_WAIT()
Definition: protothreads.h:108
bool fibre_run_atomic(fibre_t *f)
Definition: fibre.c:183
uint32_t time_now(void)
char * strtolower(char *s)
Definition: string.c:44
Definition: stats.h:41
void benchmark_show_csv(benchmark_results_t *results, FILE *f)
Definition: libbench.c:236
void benchmark_init(benchmark_results_t *results, fibre_t *wakeup)
Definition: libbench.c:151
#define PT_BEGIN_FIBRE(f)
Fibre aware alternative to PT_BEGIN().
Definition: fibre.h:103
unsigned int cycles
Definition: libbench.c:39
#define NUM_CYCLES
Definition: libbench.c:31
fibre_t * wakeup
Definition: libbench.h:42
Fibre descriptor.
Definition: fibre.h:64
statval_t stats_mean(stats_t *s)
Definition: stats.c:42
void fibre_run(fibre_t *f)
Definition: fibre.c:173