diff options
Diffstat (limited to 'harness.c')
-rw-r--r-- | harness.c | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/harness.c b/harness.c new file mode 100644 index 0000000..846e7e9 --- /dev/null +++ b/harness.c @@ -0,0 +1,325 @@ +/* + * Copyright (c) 2011, Linaro Limited + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Linaro nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** A simple harness that times how long a string function takes to + * run. + */ + +/* PENDING: Add EPL */ + +#include <string.h> +#include <time.h> +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include <assert.h> +#include <unistd.h> +#include <assert.h> + +#define NUM_ELEMS(_x) (sizeof(_x) / sizeof((_x)[0])) + +#ifndef VERSION +#define VERSION "(unknown version)" +#endif + +/** Make sure a function is called by using the return value */ +#define SPOIL(_x) volatile int x = (int)(_x); (void)x + +/** Type of functions that can be tested */ +typedef void (*stub_t)(void *dest, void *src, size_t n); + +/** Meta data about one test */ +struct test +{ + /** Test name */ + const char *name; + /** Function to test */ + stub_t stub; +}; + +/** Flush the cache by reading a chunk of memory */ +static void empty(volatile char *against) +{ + /* We know that there's a 16 k cache with 64 byte lines giving + a total of 256 lines. Read randomly from 256*5 places should + flush everything */ + int offset = (1024 - 256)*1024; + + for (int i = offset; i < offset + 16*1024*3; i += 64) + { + against[i]; + } +} + +/** Stub that does nothing. Used for calibrating */ +static void xbounce(void *dest, void *src, size_t n) +{ + SPOIL(0); +} + +/** Stub that calls memcpy */ +static void xmemcpy(void *dest, void *src, size_t n) +{ + SPOIL(memcpy(dest, src, n)); +} + +/** Stub that calls memset */ +static void xmemset(void *dest, void *src, size_t n) +{ + SPOIL(memset(dest, 0, n)); +} + +/** Stub that calls strcpy */ +static void xstrcpy(void *dest, void *src, size_t n) +{ + SPOIL(strcpy(dest, src)); +} + +/** Stub that calls strlen */ +static void xstrlen(void *dest, void *src, size_t n) +{ + SPOIL(strlen(dest)); +} + +/** Stub that calls strcmp */ +static void xstrcmp(void *dest, void *src, size_t n) +{ + SPOIL(strcmp(dest, src)); +} + +/** Stub that calls strchr */ +static void xstrchr(void *dest, void *src, size_t n) +{ + /* Put the character at the end of the string and before the null */ + ((char *)src)[n-1] = 32; + SPOIL(strchr(src, 32)); +} + +/** Stub that calls memchr */ +static void xmemchr(void *dest, void *src, size_t n) +{ + /* Put the character at the end of the block */ + ((char *)src)[n-1] = 32; + SPOIL(memchr(src, 32, n)); +} + +/** All functions that can be tested */ +static const struct test tests[] = + { + { "bounce", xbounce }, + { "memchr", xmemchr }, + { "memcpy", xmemcpy }, + { "memset", xmemset }, + { "strchr", xstrchr }, + { "strcmp", xstrcmp }, + { "strcpy", xstrcpy }, + { "strlen", xstrlen }, + { NULL } + }; + +/** Show basic usage */ +static void usage(const char* name) +{ + printf("%s %s: run a string related benchmark.\n" + "usage: %s [-c block-size] [-l loop-count] [-a alignment] [-f] [-t test-name]\n" + , name, VERSION, name); + + printf("Tests:"); + + for (const struct test *ptest = tests; ptest->name != NULL; ptest++) + { + printf(" %s", ptest->name); + } + + printf("\n"); + + exit(-1); +} + +/** Find the test by name */ +static const struct test *find_test(const char *name) +{ + if (name == NULL) + { + return tests + 0; + } + else + { + for (const struct test *p = tests; p->name != NULL; p++) + { + if (strcmp(p->name, name) == 0) + { + return p; + } + } + } + + return NULL; +} + +/** Take a pointer and ensure that the lower bits == alignment */ +static char *realign(char *p, int alignment) +{ + if (alignment < 0) + { + return p; + } + + uintptr_t pp = (uintptr_t)p; + pp = (pp + 255) & ~255; + pp += alignment; + + return (char *)pp; +} + +/** Setup and run a test */ +int main(int argc, char **argv) +{ + /* Buffers to read and write from */ + char *src = calloc(1024, 1024); + char *dest = calloc(1024, 1024); + + assert(src != NULL && dest != NULL); + + /* Number of bytes per call */ + int count = 31; + /* Number of times to run */ + int loops = 10000000; + /* True to flush the cache each time */ + int flush = 0; + /* Name of the test */ + const char *name = NULL; + /* Alignment of both buffers */ + int alignment = -1; + + int opt; + + while ((opt = getopt(argc, argv, "c:l:ft:hva:")) > 0) + { + switch (opt) + { + case 'c': + count = atoi(optarg); + break; + case 'l': + loops = atoi(optarg); + break; + case 'a': + alignment = atoi(optarg); + break; + case 'f': + flush = 1; + break; + case 't': + name = strdup(optarg); + break; + case 'h': + usage(argv[0]); + break; + default: + usage(argv[0]); + break; + } + } + + /* Find the test by name */ + const struct test *ptest = find_test(name); + + if (ptest == NULL) + { + usage(argv[0]); + } + + src = realign(src, alignment); + dest = realign(dest, alignment); + + /* Fill the first 16 k with non-zero, reproducable random data */ + srandom(1539); + + for (int i = 0; i < 16*1024; i++) + { + src[i] = (char)random() | 1; + dest[i] = src[i]; + } + + /* Make sure the buffers are null terminated for any string tests */ + src[count] = 0; + dest[count] = 0; + + struct timespec start, end; + int err = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start); + assert(err == 0); + + /* Preload */ + stub_t stub = ptest->stub; + + /* Run two variants to reduce the cost of testing for the flush */ + if (flush == 0) + { + for (int i = 0; i < loops; i++) + { + (*stub)(dest, src, count); + } + } + else + { + for (int i = 0; i < loops; i++) + { + (*stub)(dest, src, count); + empty(dest); + } + } + + err = clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end); + assert(err == 0); + + /* Drop any leading path and pull the variant name out of the executable */ + char *variant = strrchr(argv[0], '/'); + + if (variant == NULL) + { + variant = argv[0]; + } + + variant = strstr(variant, "try-"); + assert(variant != NULL); + + double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) * 1e-9; + /* Estimate the bounce time. Measured on a Panda. */ + double bounced = 0.448730 * loops / 50000000; + + /* Dump both machine and human readable versions */ + printf("%s:%s:%u:%u:%d:%.6f: took %.6f s for %u calls to %s of %u bytes. ~%.3f MB/s corrected.\n", + variant + 4, ptest->name, + count, loops, alignment, + elapsed, + elapsed, loops, ptest->name, count, + (double)loops*count/(elapsed - bounced)/(1024*1024)); + + return 0; +} |