summaryrefslogtreecommitdiff
path: root/harness.c
diff options
context:
space:
mode:
Diffstat (limited to 'harness.c')
-rw-r--r--harness.c325
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;
+}