From b76d85a4d2e61546b40135db141972eba4ecd981 Mon Sep 17 00:00:00 2001 From: Jens Axboe Date: Fri, 1 Dec 2017 11:13:23 -0700 Subject: [PATCH 1/1] Add basic memcpy test Research is ongoing in how to improve (and make deterministic) the memcpy speed, since it's especially applicable to the persistent memory engines. Not documented yet. Signed-off-by: Jens Axboe --- init.c | 11 +++ lib/memcpy.c | 263 +++++++++++++++++++++++++++++++++++++++++++++++++++ lib/memcpy.h | 6 ++ 3 files changed, 280 insertions(+) create mode 100644 lib/memcpy.c create mode 100644 lib/memcpy.h diff --git a/init.c b/init.c index 607f7e0c..c34bd159 100644 --- a/init.c +++ b/init.c @@ -32,6 +32,7 @@ #include "crc/test.h" #include "lib/pow2.h" +#include "lib/memcpy.h" const char fio_version_string[] = FIO_VERSION; @@ -233,6 +234,11 @@ static struct option l_opts[FIO_NR_OPTIONS] = { .has_arg = optional_argument, .val = 'G', }, + { + .name = (char *) "memcpytest", + .has_arg = optional_argument, + .val = 'M', + }, { .name = (char *) "idle-prof", .has_arg = required_argument, @@ -2731,6 +2737,11 @@ int parse_cmd_line(int argc, char *argv[], int client_type) do_exit++; exit_val = fio_crctest(optarg); break; + case 'M': + did_arg = true; + do_exit++; + exit_val = fio_memcpy_test(optarg); + break; case 'L': { long long val; diff --git a/lib/memcpy.c b/lib/memcpy.c new file mode 100644 index 00000000..f937dd9f --- /dev/null +++ b/lib/memcpy.c @@ -0,0 +1,263 @@ +#include +#include +#include + +#include "memcpy.h" +#include "rand.h" +#include "../fio_time.h" +#include "../gettime.h" +#include "../fio.h" + +#define BUF_SIZE 32 * 1024 * 1024ULL + +#define NR_ITERS 64 + +struct memcpy_test { + const char *name; + void *src; + void *dst; + size_t size; +}; + +static struct memcpy_test tests[] = { + { + .name = "8 bytes", + .size = 8, + }, + { + .name = "16 bytes", + .size = 16, + }, + { + .name = "96 bytes", + .size = 96, + }, + { + .name = "128 bytes", + .size = 128, + }, + { + .name = "256 bytes", + .size = 256, + }, + { + .name = "512 bytes", + .size = 512, + }, + { + .name = "2048 bytes", + .size = 2048, + }, + { + .name = "8192 bytes", + .size = 8192, + }, + { + .name = "131072 bytes", + .size = 131072, + }, + { + .name = "262144 bytes", + .size = 262144, + }, + { + .name = "524288 bytes", + .size = 524288, + }, + { + .name = NULL, + }, +}; + +struct memcpy_type { + const char *name; + unsigned int mask; + void (*fn)(struct memcpy_type *, struct memcpy_test *); +}; + +enum { + T_MEMCPY = 1U << 0, + T_MEMMOVE = 1U << 1, + T_SIMPLE = 1U << 2, +}; + +#define do_test(t, test, fn) do { \ + size_t left, this; \ + void *src, *dst; \ + int i; \ + \ + for (i = 0; i < NR_ITERS; i++) { \ + left = BUF_SIZE; \ + src = test->src; \ + dst = test->dst; \ + while (left) { \ + this = test->size; \ + if (this > left) \ + this = left; \ + (fn)(dst, src, this); \ + left -= this; \ + src += this; \ + dst += this; \ + } \ + } \ +} while (0) + +static void t_memcpy(struct memcpy_type *t, struct memcpy_test *test) +{ + do_test(t, test, memcpy); +} + +static void t_memmove(struct memcpy_type *t, struct memcpy_test *test) +{ + do_test(t, test, memmove); +} + +static void simple_memcpy(void *dst, void const *src, size_t len) +{ + char *d = dst; + const char *s = src; + + while (len--) + *d++ = *s++; +} + +static void t_simple(struct memcpy_type *t, struct memcpy_test *test) +{ + do_test(t, test, simple_memcpy); +} + +static struct memcpy_type t[] = { + { + .name = "memcpy", + .mask = T_MEMCPY, + .fn = t_memcpy, + }, + { + .name = "memmove", + .mask = T_MEMMOVE, + .fn = t_memmove, + }, + { + .name = "simple", + .mask = T_SIMPLE, + .fn = t_simple, + }, + + { + .name = NULL, + }, +}; + +static unsigned int get_test_mask(const char *type) +{ + char *ostr, *str = strdup(type); + unsigned int mask; + char *name; + int i; + + ostr = str; + mask = 0; + while ((name = strsep(&str, ",")) != NULL) { + for (i = 0; t[i].name; i++) { + if (!strcmp(t[i].name, name)) { + mask |= t[i].mask; + break; + } + } + } + + free(ostr); + return mask; +} + +static int list_types(void) +{ + int i; + + for (i = 0; t[i].name; i++) + printf("%s\n", t[i].name); + + return 1; +} + +static int setup_tests(void) +{ + struct memcpy_test *test; + struct frand_state state; + void *src, *dst; + int i; + + if (posix_memalign(&src, page_size, BUF_SIZE)) + return 1; + if (posix_memalign(&dst, page_size, BUF_SIZE)) + return 1; + + init_rand_seed(&state, 0x8989, 0); + fill_random_buf(&state, src, BUF_SIZE); + + for (i = 0; tests[i].name; i++) { + test = &tests[i]; + test->src = src; + test->dst = dst; + } + + return 0; +} + +int fio_memcpy_test(const char *type) +{ + unsigned int test_mask = 0; + int j, i; + + if (!type) + test_mask = ~0U; + else if (!strcmp(type, "help") || !strcmp(type, "list")) + return list_types(); + else + test_mask = get_test_mask(type); + + if (!test_mask) { + fprintf(stderr, "fio: unknown hash `%s`. Available:\n", type); + return list_types(); + } + + if (setup_tests()) { + fprintf(stderr, "setting up mem regions failed\n"); + return 1; + } + + for (i = 0; t[i].name; i++) { + struct timespec ts; + double mb_sec; + uint64_t usec; + + if (!(t[i].mask & test_mask)) + continue; + + /* + * For first run, make sure CPUs are spun up and that + * we've touched the data. + */ + usec_spin(100000); + t[i].fn(&t[i], &tests[0]); + + printf("%s\n", t[i].name); + + for (j = 0; tests[j].name; j++) { + fio_gettime(&ts, NULL); + t[i].fn(&t[i], &tests[j]); + usec = utime_since_now(&ts); + + if (usec) { + unsigned long long mb = NR_ITERS * BUF_SIZE; + + mb_sec = (double) mb / (double) usec; + mb_sec /= (1.024 * 1.024); + printf("\t%s:\t%8.2f MiB/sec\n", tests[j].name, mb_sec); + } else + printf("\t%s:inf MiB/sec\n", tests[j].name); + } + } + + return 0; +} diff --git a/lib/memcpy.h b/lib/memcpy.h new file mode 100644 index 00000000..f61a4a09 --- /dev/null +++ b/lib/memcpy.h @@ -0,0 +1,6 @@ +#ifndef FIO_MEMCPY_H +#define FIO_MEMCPY_H + +int fio_memcpy_test(const char *type); + +#endif -- 2.25.1