1 | /* Benchmark malloc and free functions. |
2 | Copyright (C) 2013-2022 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | #include <errno.h> |
20 | #include <math.h> |
21 | #include <pthread.h> |
22 | #include <signal.h> |
23 | #include <stdio.h> |
24 | #include <stdlib.h> |
25 | #include <string.h> |
26 | #include <sys/time.h> |
27 | #include <sys/resource.h> |
28 | #include <unistd.h> |
29 | |
30 | #include "bench-timing.h" |
31 | #include "json-lib.h" |
32 | |
33 | /* Benchmark duration in seconds. */ |
34 | #define BENCHMARK_DURATION 10 |
35 | #define RAND_SEED 88 |
36 | |
37 | #ifndef NUM_THREADS |
38 | # define NUM_THREADS 1 |
39 | #endif |
40 | |
41 | /* Maximum memory that can be allocated at any one time is: |
42 | |
43 | NUM_THREADS * WORKING_SET_SIZE * MAX_ALLOCATION_SIZE |
44 | |
45 | However due to the distribution of the random block sizes |
46 | the typical amount allocated will be much smaller. */ |
47 | #define WORKING_SET_SIZE 1024 |
48 | |
49 | #define MIN_ALLOCATION_SIZE 4 |
50 | #define MAX_ALLOCATION_SIZE 32768 |
51 | |
52 | /* Get a random block size with an inverse square distribution. */ |
53 | static unsigned int |
54 | get_block_size (unsigned int rand_data) |
55 | { |
56 | /* Inverse square. */ |
57 | const float exponent = -2; |
58 | /* Minimum value of distribution. */ |
59 | const float dist_min = MIN_ALLOCATION_SIZE; |
60 | /* Maximum value of distribution. */ |
61 | const float dist_max = MAX_ALLOCATION_SIZE; |
62 | |
63 | float min_pow = powf (x: dist_min, y: exponent + 1); |
64 | float max_pow = powf (x: dist_max, y: exponent + 1); |
65 | |
66 | float r = (float) rand_data / RAND_MAX; |
67 | |
68 | return (unsigned int) powf (x: (max_pow - min_pow) * r + min_pow, |
69 | y: 1 / (exponent + 1)); |
70 | } |
71 | |
72 | #define NUM_BLOCK_SIZES 8000 |
73 | #define NUM_OFFSETS ((WORKING_SET_SIZE) * 4) |
74 | |
75 | static unsigned int random_block_sizes[NUM_BLOCK_SIZES]; |
76 | static unsigned int random_offsets[NUM_OFFSETS]; |
77 | |
78 | static void |
79 | init_random_values (void) |
80 | { |
81 | for (size_t i = 0; i < NUM_BLOCK_SIZES; i++) |
82 | random_block_sizes[i] = get_block_size (rand_data: rand ()); |
83 | |
84 | for (size_t i = 0; i < NUM_OFFSETS; i++) |
85 | random_offsets[i] = rand () % WORKING_SET_SIZE; |
86 | } |
87 | |
88 | static unsigned int |
89 | get_random_block_size (unsigned int *state) |
90 | { |
91 | unsigned int idx = *state; |
92 | |
93 | if (idx >= NUM_BLOCK_SIZES - 1) |
94 | idx = 0; |
95 | else |
96 | idx++; |
97 | |
98 | *state = idx; |
99 | |
100 | return random_block_sizes[idx]; |
101 | } |
102 | |
103 | static unsigned int |
104 | get_random_offset (unsigned int *state) |
105 | { |
106 | unsigned int idx = *state; |
107 | |
108 | if (idx >= NUM_OFFSETS - 1) |
109 | idx = 0; |
110 | else |
111 | idx++; |
112 | |
113 | *state = idx; |
114 | |
115 | return random_offsets[idx]; |
116 | } |
117 | |
118 | static volatile bool timeout; |
119 | |
120 | static void |
121 | alarm_handler (int signum) |
122 | { |
123 | timeout = true; |
124 | } |
125 | |
126 | /* Allocate and free blocks in a random order. */ |
127 | static size_t |
128 | malloc_benchmark_loop (void **ptr_arr) |
129 | { |
130 | unsigned int offset_state = 0, block_state = 0; |
131 | size_t iters = 0; |
132 | |
133 | while (!timeout) |
134 | { |
135 | unsigned int next_idx = get_random_offset (state: &offset_state); |
136 | unsigned int next_block = get_random_block_size (state: &block_state); |
137 | |
138 | free (ptr: ptr_arr[next_idx]); |
139 | |
140 | ptr_arr[next_idx] = malloc (size: next_block); |
141 | |
142 | iters++; |
143 | } |
144 | |
145 | return iters; |
146 | } |
147 | |
148 | struct thread_args |
149 | { |
150 | size_t iters; |
151 | void **working_set; |
152 | timing_t elapsed; |
153 | }; |
154 | |
155 | static void * |
156 | benchmark_thread (void *arg) |
157 | { |
158 | struct thread_args *args = (struct thread_args *) arg; |
159 | size_t iters; |
160 | void *thread_set = args->working_set; |
161 | timing_t start, stop; |
162 | |
163 | TIMING_NOW (start); |
164 | iters = malloc_benchmark_loop (ptr_arr: thread_set); |
165 | TIMING_NOW (stop); |
166 | |
167 | TIMING_DIFF (args->elapsed, start, stop); |
168 | args->iters = iters; |
169 | |
170 | return NULL; |
171 | } |
172 | |
173 | static timing_t |
174 | do_benchmark (size_t num_threads, size_t *iters) |
175 | { |
176 | timing_t elapsed = 0; |
177 | |
178 | if (num_threads == 1) |
179 | { |
180 | timing_t start, stop; |
181 | void *working_set[WORKING_SET_SIZE]; |
182 | |
183 | memset (working_set, 0, sizeof (working_set)); |
184 | |
185 | TIMING_NOW (start); |
186 | *iters = malloc_benchmark_loop (ptr_arr: working_set); |
187 | TIMING_NOW (stop); |
188 | |
189 | TIMING_DIFF (elapsed, start, stop); |
190 | } |
191 | else |
192 | { |
193 | struct thread_args args[num_threads]; |
194 | void *working_set[num_threads][WORKING_SET_SIZE]; |
195 | pthread_t threads[num_threads]; |
196 | |
197 | memset (working_set, 0, sizeof (working_set)); |
198 | |
199 | *iters = 0; |
200 | |
201 | for (size_t i = 0; i < num_threads; i++) |
202 | { |
203 | args[i].working_set = working_set[i]; |
204 | pthread_create(newthread: &threads[i], NULL, start_routine: benchmark_thread, arg: &args[i]); |
205 | } |
206 | |
207 | for (size_t i = 0; i < num_threads; i++) |
208 | { |
209 | pthread_join(th: threads[i], NULL); |
210 | TIMING_ACCUM (elapsed, args[i].elapsed); |
211 | *iters += args[i].iters; |
212 | } |
213 | } |
214 | return elapsed; |
215 | } |
216 | |
217 | static void usage(const char *name) |
218 | { |
219 | fprintf (stderr, "%s: <num_threads>\n" , name); |
220 | exit (1); |
221 | } |
222 | |
223 | int |
224 | main (int argc, char **argv) |
225 | { |
226 | timing_t cur; |
227 | size_t iters = 0, num_threads = 1; |
228 | json_ctx_t json_ctx; |
229 | double d_total_s, d_total_i; |
230 | struct sigaction act; |
231 | |
232 | if (argc == 1) |
233 | num_threads = 1; |
234 | else if (argc == 2) |
235 | { |
236 | long ret; |
237 | |
238 | errno = 0; |
239 | ret = strtol(argv[1], NULL, 10); |
240 | |
241 | if (errno || ret == 0) |
242 | usage(name: argv[0]); |
243 | |
244 | num_threads = ret; |
245 | } |
246 | else |
247 | usage(name: argv[0]); |
248 | |
249 | init_random_values (); |
250 | |
251 | json_init (ctx: &json_ctx, indent_level: 0, stdout); |
252 | |
253 | json_document_begin (ctx: &json_ctx); |
254 | |
255 | json_attr_string (ctx: &json_ctx, name: "timing_type" , TIMING_TYPE); |
256 | |
257 | json_attr_object_begin (ctx: &json_ctx, name: "functions" ); |
258 | |
259 | json_attr_object_begin (ctx: &json_ctx, name: "malloc" ); |
260 | |
261 | json_attr_object_begin (ctx: &json_ctx, name: "" ); |
262 | |
263 | memset (&act, 0, sizeof (act)); |
264 | act.sa_handler = &alarm_handler; |
265 | |
266 | sigaction (SIGALRM, act: &act, NULL); |
267 | |
268 | alarm (BENCHMARK_DURATION); |
269 | |
270 | cur = do_benchmark (num_threads, iters: &iters); |
271 | |
272 | struct rusage usage; |
273 | getrusage(RUSAGE_SELF, usage: &usage); |
274 | |
275 | d_total_s = cur; |
276 | d_total_i = iters; |
277 | |
278 | json_attr_double (ctx: &json_ctx, name: "duration" , d: d_total_s); |
279 | json_attr_double (ctx: &json_ctx, name: "iterations" , d: d_total_i); |
280 | json_attr_double (ctx: &json_ctx, name: "time_per_iteration" , d: d_total_s / d_total_i); |
281 | json_attr_double (ctx: &json_ctx, name: "max_rss" , d: usage.ru_maxrss); |
282 | |
283 | json_attr_double (ctx: &json_ctx, name: "threads" , d: num_threads); |
284 | json_attr_double (ctx: &json_ctx, name: "min_size" , MIN_ALLOCATION_SIZE); |
285 | json_attr_double (ctx: &json_ctx, name: "max_size" , MAX_ALLOCATION_SIZE); |
286 | json_attr_double (ctx: &json_ctx, name: "random_seed" , RAND_SEED); |
287 | |
288 | json_attr_object_end (ctx: &json_ctx); |
289 | |
290 | json_attr_object_end (ctx: &json_ctx); |
291 | |
292 | json_attr_object_end (ctx: &json_ctx); |
293 | |
294 | json_document_end (ctx: &json_ctx); |
295 | |
296 | return 0; |
297 | } |
298 | |