1 /*
2 * Copyright (c) 2011, Linaro Limited
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the Linaro nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 /** A simple harness that times how long a string function takes to
29 * run.
30 */
31
32 /* PENDING: Add EPL */
33
34 #include <string.h>
35 #include <time.h>
36 #include <stdint.h>
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <stdbool.h>
40 #include <assert.h>
41 #include <unistd.h>
42 #include <errno.h>
43
44 #define NUM_ELEMS(_x) (sizeof(_x) / sizeof((_x)[0]))
45
46 #ifndef VERSION
47 #define VERSION "(unknown version)"
48 #endif
49
50 /** Make sure a function is called by using the return value */
51 #define SPOIL(_x) volatile long x = (long)(_x); (void)x
52
53 /** Type of functions that can be tested */
54 typedef void (*stub_t)(void *dest, void *src, size_t n);
55
56 /** Meta data about one test */
57 struct test
58 {
59 /** Test name */
60 const char *name;
61 /** Function to test */
62 stub_t stub;
63 };
64
65 /** Flush the cache by reading a chunk of memory */
empty(volatile char * against)66 static void empty(volatile char *against)
67 {
68 /* We know that there's a 16 k cache with 64 byte lines giving
69 a total of 256 lines. Read randomly from 256*5 places should
70 flush everything */
71 int offset = (1024 - 256)*1024;
72
73 for (int i = offset; i < offset + 16*1024*3; i += 64)
74 {
75 against[i];
76 }
77 }
78
79 /** Stub that does nothing. Used for calibrating */
xbounce(void * dest,void * src,size_t n)80 static void xbounce(void *dest, void *src, size_t n)
81 {
82 SPOIL(0);
83 }
84
85 /** Stub that calls memcpy */
xmemcpy(void * dest,void * src,size_t n)86 static void xmemcpy(void *dest, void *src, size_t n)
87 {
88 SPOIL(memcpy(dest, src, n));
89 }
90
91 /** Stub that calls memset */
xmemset(void * dest,void * src,size_t n)92 static void xmemset(void *dest, void *src, size_t n)
93 {
94 SPOIL(memset(dest, 0, n));
95 }
96
97 /** Stub that calls memcmp */
xmemcmp(void * dest,void * src,size_t n)98 static void xmemcmp(void *dest, void *src, size_t n)
99 {
100 SPOIL(memcmp(dest, src, n));
101 }
102
103 /** Stub that calls strcpy */
xstrcpy(void * dest,void * src,size_t n)104 static void xstrcpy(void *dest, void *src, size_t n)
105 {
106 SPOIL(strcpy(dest, src));
107 }
108
109 /** Stub that calls strlen */
xstrlen(void * dest,void * src,size_t n)110 static void xstrlen(void *dest, void *src, size_t n)
111 {
112 SPOIL(strlen(dest));
113 }
114
115 /** Stub that calls strcmp */
xstrcmp(void * dest,void * src,size_t n)116 static void xstrcmp(void *dest, void *src, size_t n)
117 {
118 SPOIL(strcmp(dest, src));
119 }
120
121 /** Stub that calls strchr */
xstrchr(void * dest,void * src,size_t n)122 static void xstrchr(void *dest, void *src, size_t n)
123 {
124 /* Put the character at the end of the string and before the null */
125 ((char *)src)[n-1] = 32;
126 SPOIL(strchr(src, 32));
127 }
128
129 /** Stub that calls memchr */
xmemchr(void * dest,void * src,size_t n)130 static void xmemchr(void *dest, void *src, size_t n)
131 {
132 /* Put the character at the end of the block */
133 ((char *)src)[n-1] = 32;
134 SPOIL(memchr(src, 32, n));
135 }
136
137 /** All functions that can be tested */
138 static const struct test tests[] =
139 {
140 { "bounce", xbounce },
141 { "memchr", xmemchr },
142 { "memcpy", xmemcpy },
143 { "memset", xmemset },
144 { "memcmp", xmemcmp },
145 { "strchr", xstrchr },
146 { "strcmp", xstrcmp },
147 { "strcpy", xstrcpy },
148 { "strlen", xstrlen },
149 { NULL }
150 };
151
152 /** Show basic usage */
usage(const char * name)153 static void usage(const char* name)
154 {
155 printf("%s %s: run a string related benchmark.\n"
156 "usage: %s [-c block-size] [-l loop-count] [-a alignment|src_alignment:dst_alignment] [-f] [-t test-name] [-r run-id]\n"
157 , name, VERSION, name);
158
159 printf("Tests:");
160
161 for (const struct test *ptest = tests; ptest->name != NULL; ptest++)
162 {
163 printf(" %s", ptest->name);
164 }
165
166 printf("\n");
167
168 exit(-1);
169 }
170
171 /** Find the test by name */
find_test(const char * name)172 static const struct test *find_test(const char *name)
173 {
174 if (name == NULL)
175 {
176 return tests + 0;
177 }
178 else
179 {
180 for (const struct test *p = tests; p->name != NULL; p++)
181 {
182 if (strcmp(p->name, name) == 0)
183 {
184 return p;
185 }
186 }
187 }
188
189 return NULL;
190 }
191
192 #define MIN_BUFFER_SIZE 1024*1024
193 #define MAX_ALIGNMENT 256
194
195 /** Take a pointer and ensure that the lower bits == alignment */
realign(char * p,int alignment)196 static char *realign(char *p, int alignment)
197 {
198 uintptr_t pp = (uintptr_t)p;
199 pp = (pp + (MAX_ALIGNMENT - 1)) & ~(MAX_ALIGNMENT - 1);
200 pp += alignment;
201
202 return (char *)pp;
203 }
204
parse_int_arg(const char * arg,const char * exe_name)205 static int parse_int_arg(const char *arg, const char *exe_name)
206 {
207 long int ret;
208
209 errno = 0;
210 ret = strtol(arg, NULL, 0);
211
212 if (errno)
213 {
214 usage(exe_name);
215 }
216
217 return (int)ret;
218 }
219
parse_alignment_arg(const char * arg,const char * exe_name,int * src_alignment,int * dst_alignment)220 static void parse_alignment_arg(const char *arg, const char *exe_name,
221 int *src_alignment, int *dst_alignment)
222 {
223 long int ret;
224 char *endptr;
225
226 errno = 0;
227 ret = strtol(arg, &endptr, 0);
228
229 if (errno)
230 {
231 usage(exe_name);
232 }
233
234 *src_alignment = (int)ret;
235
236 if (ret > 256 || ret < 1)
237 {
238 printf("Alignment should be in the range [1, 256].\n");
239 usage(exe_name);
240 }
241
242 if (ret == 256)
243 ret = 0;
244
245 if (endptr && *endptr == ':')
246 {
247 errno = 0;
248 ret = strtol(endptr + 1, NULL, 0);
249
250 if (errno)
251 {
252 usage(exe_name);
253 }
254
255 if (ret > 256 || ret < 1)
256 {
257 printf("Alignment should be in the range [1, 256].\n");
258 usage(exe_name);
259 }
260
261 if (ret == 256)
262 ret = 0;
263 }
264
265 *dst_alignment = (int)ret;
266 }
267
268 /** Setup and run a test */
main(int argc,char ** argv)269 int main(int argc, char **argv)
270 {
271 /* Size of src and dest buffers */
272 size_t buffer_size = MIN_BUFFER_SIZE;
273
274 /* Number of bytes per call */
275 int count = 31;
276 /* Number of times to run */
277 int loops = 10000000;
278 /* True to flush the cache each time */
279 int flush = 0;
280 /* Name of the test */
281 const char *name = NULL;
282 /* Alignment of buffers */
283 int src_alignment = 8;
284 int dst_alignment = 8;
285 /* Name of the run */
286 const char *run_id = "0";
287
288 int opt;
289
290 while ((opt = getopt(argc, argv, "c:l:ft:r:hva:")) > 0)
291 {
292 switch (opt)
293 {
294 case 'c':
295 count = parse_int_arg(optarg, argv[0]);
296 break;
297 case 'l':
298 loops = parse_int_arg(optarg, argv[0]);
299 break;
300 case 'a':
301 parse_alignment_arg(optarg, argv[0], &src_alignment, &dst_alignment);
302 break;
303 case 'f':
304 flush = 1;
305 break;
306 case 't':
307 name = strdup(optarg);
308 break;
309 case 'r':
310 run_id = strdup(optarg);
311 break;
312 case 'h':
313 usage(argv[0]);
314 break;
315 default:
316 usage(argv[0]);
317 break;
318 }
319 }
320
321 /* Find the test by name */
322 const struct test *ptest = find_test(name);
323
324 if (ptest == NULL)
325 {
326 usage(argv[0]);
327 }
328
329 if (count + MAX_ALIGNMENT * 2 > MIN_BUFFER_SIZE)
330 {
331 buffer_size = count + MAX_ALIGNMENT * 2;
332 }
333
334 /* Buffers to read and write from */
335 char *src = malloc(buffer_size);
336 char *dest = malloc(buffer_size);
337
338 assert(src != NULL && dest != NULL);
339
340 src = realign(src, src_alignment);
341 dest = realign(dest, dst_alignment);
342
343 /* Fill the buffer with non-zero, reproducable random data */
344 srandom(1539);
345
346 for (int i = 0; i < buffer_size; i++)
347 {
348 src[i] = (char)random() | 1;
349 dest[i] = src[i];
350 }
351
352 /* Make sure the buffers are null terminated for any string tests */
353 src[count] = 0;
354 dest[count] = 0;
355
356 struct timespec start, end;
357 int err = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start);
358 assert(err == 0);
359
360 /* Preload */
361 stub_t stub = ptest->stub;
362
363 /* Run two variants to reduce the cost of testing for the flush */
364 if (flush == 0)
365 {
366 for (int i = 0; i < loops; i++)
367 {
368 (*stub)(dest, src, count);
369 }
370 }
371 else
372 {
373 for (int i = 0; i < loops; i++)
374 {
375 (*stub)(dest, src, count);
376 empty(dest);
377 }
378 }
379
380 err = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end);
381 assert(err == 0);
382
383 /* Drop any leading path and pull the variant name out of the executable */
384 char *variant = strrchr(argv[0], '/');
385
386 if (variant == NULL)
387 {
388 variant = argv[0];
389 }
390
391 variant = strstr(variant, "try-");
392 assert(variant != NULL);
393
394 double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) * 1e-9;
395 /* Estimate the bounce time. Measured on a Panda. */
396 double bounced = 0.448730 * loops / 50000000;
397
398 /* Dump both machine and human readable versions */
399 printf("%s:%s:%u:%u:%d:%d:%s:%.6f: took %.6f s for %u calls to %s of %u bytes. ~%.3f MB/s corrected.\n",
400 variant + 4, ptest->name,
401 count, loops, src_alignment, dst_alignment, run_id,
402 elapsed,
403 elapsed, loops, ptest->name, count,
404 (double)loops*count/(elapsed - bounced)/(1024*1024));
405
406 return 0;
407 }
408