[PATCH] perf mmap and memory write test

From: Tim Chen
Date: Wed Oct 30 2013 - 08:18:29 EST


This patch add a perf benchmark to mmap a piece of memory,
write to the memory and unmap the memory for a given
number of threads. The threads are distributed and pinned
evenly to the sockets on the machine. The options
for the benchmark are as follow:

usage: perf bench mem mmap <options>

-l, --length <1MB> Specify length of memory to set. Available units: B, KB, MB, GB and TB (upper and lower)
-i, --iterations <n> repeat mmap() invocation this number of times
-n, --threads <n> number of threads doing mmap() invocation
-r, --runtime <n> runtime per iteration in sec
-w, --warmup <n> warmup time in sec
-s, --serialize serialize the mmap() operations with mutex
-v, --verbose verbose output giving info about each iteration

Signed-off-by: Tim Chen <tim.c.chen@xxxxxxxxxxxxxxx>
---
tools/perf/Makefile | 1 +
tools/perf/bench/bench.h | 1 +
tools/perf/bench/mem-mmap.c | 312 ++++++++++++++++++++++++++++++++++++++++++++
tools/perf/builtin-bench.c | 3 +
4 files changed, 317 insertions(+)
create mode 100644 tools/perf/bench/mem-mmap.c

diff --git a/tools/perf/Makefile b/tools/perf/Makefile
index 64c043b..80e32d1 100644
--- a/tools/perf/Makefile
+++ b/tools/perf/Makefile
@@ -408,6 +408,7 @@ BUILTIN_OBJS += $(OUTPUT)bench/mem-memset-x86-64-asm.o
endif
BUILTIN_OBJS += $(OUTPUT)bench/mem-memcpy.o
BUILTIN_OBJS += $(OUTPUT)bench/mem-memset.o
+BUILTIN_OBJS += $(OUTPUT)bench/mem-mmap.o

BUILTIN_OBJS += $(OUTPUT)builtin-diff.o
BUILTIN_OBJS += $(OUTPUT)builtin-evlist.o
diff --git a/tools/perf/bench/bench.h b/tools/perf/bench/bench.h
index 0fdc852..dbd0515 100644
--- a/tools/perf/bench/bench.h
+++ b/tools/perf/bench/bench.h
@@ -31,6 +31,7 @@ extern int bench_sched_pipe(int argc, const char **argv, const char *prefix);
extern int bench_mem_memcpy(int argc, const char **argv,
const char *prefix __maybe_unused);
extern int bench_mem_memset(int argc, const char **argv, const char *prefix);
+extern int bench_mem_mmap(int argc, const char **argv, const char *prefix);

#define BENCH_FORMAT_DEFAULT_STR "default"
#define BENCH_FORMAT_DEFAULT 0
diff --git a/tools/perf/bench/mem-mmap.c b/tools/perf/bench/mem-mmap.c
new file mode 100644
index 0000000..11d96ad
--- /dev/null
+++ b/tools/perf/bench/mem-mmap.c
@@ -0,0 +1,312 @@
+/*
+ * mem-mmap.c
+ *
+ * memset: Simple parallel mmap and touch maped memory
+ */
+
+#include "../perf.h"
+#include "../util/util.h"
+#include "../util/parse-options.h"
+#include "../util/header.h"
+#include "bench.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <math.h>
+#include <unistd.h>
+#include <errno.h>
+#include <pthread.h>
+#include <numa.h>
+#include <numaif.h>
+#include <sched.h>
+
+#define K 1024
+#define CACHELINE_SIZE 128
+
+static const char *length_str = "1MB";
+static int iterations = 10;
+static int threads = 1;
+static int stride = 8;
+static int warmup = 5;
+static int runtime = 10;
+static bool serialize_mmap = false;
+static bool verbose = false;
+static bool do_cnt = false;
+static size_t len = 1024*1024;
+static unsigned long long **results;
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+static int nr_cpus;
+static int nr_nodes;
+static struct bitmask *nodemask = NULL;
+static int *cur_cpu;
+static int cur_node;
+static struct bitmask *cpumask;
+
+static const struct option options[] = {
+ OPT_STRING('l', "length", &length_str, "1MB",
+ "Specify length of memory to set. "
+ "Available units: B, KB, MB, GB and TB (upper and lower)"),
+ OPT_INTEGER('i', "iterations", &iterations,
+ "repeat mmap() invocation this number of times"),
+ OPT_INTEGER('n', "threads", &threads,
+ "number of threads doing mmap() invocation"),
+ OPT_INTEGER('r', "runtime", &runtime,
+ "runtime per iteration in sec"),
+ OPT_INTEGER('w', "warmup", &warmup,
+ "warmup time in sec"),
+ OPT_BOOLEAN('s', "serialize", &serialize_mmap,
+ "serialize the mmap() operations with mutex"),
+ OPT_BOOLEAN('v', "verbose", &verbose,
+ "verbose output giving info about each iteration"),
+ OPT_END()
+};
+
+static const char * const bench_mem_mmap_usage[] = {
+ "perf bench mem mmap <options>",
+ NULL
+};
+
+static double timeval2double(struct timeval *ts)
+{
+ return (double)ts->tv_sec +
+ (double)ts->tv_usec / (double)1000000;
+}
+
+static void alloc_mem(void **dst, size_t length)
+{
+ if (serialize_mmap) {
+ pthread_mutex_lock(&mutex);
+ *dst = mmap(NULL, length, PROT_READ|PROT_WRITE,
+ MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+ pthread_mutex_unlock(&mutex);
+ } else
+ *dst = mmap(NULL, length, PROT_READ|PROT_WRITE,
+ MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
+ if (!*dst)
+ die("memory allocation failed - maybe length is too large?\n");
+}
+static void free_mem(void *dst, size_t length)
+{
+ if (serialize_mmap) {
+ pthread_mutex_lock(&mutex);
+ munmap(dst, length);
+ pthread_mutex_unlock(&mutex);
+ } else
+ munmap(dst, length);
+}
+
+static void *do_mmap(void *itr)
+{
+ void *dst = NULL;
+ char *c;
+ size_t l;
+ unsigned long long *cnt;
+
+ cnt = (unsigned long long *) itr;
+ while (1) {
+ /* alloc memory with mmap */
+ alloc_mem(&dst, len);
+ c = (char *) dst;
+
+ /* touch memory allocated */
+ for (l = 0; l < len; l += stride)
+ c[l] = 0xa;
+ free_mem(dst, len);
+ if (do_cnt)
+ (*cnt)++;
+ }
+ return NULL;
+}
+
+static int get_next_node(int node)
+{
+ int i;
+
+ i = (node+1) % nr_nodes;
+ while (i != node) {
+ if (numa_bitmask_isbitset(nodemask, i))
+ return i;
+ i = (i+1) % nr_nodes;
+ }
+ return (-1);
+}
+
+static int get_next_cpu(int node)
+{
+ int i, prev_cpu;
+
+ prev_cpu = cur_cpu[node];
+ i = (prev_cpu + 1) % nr_cpus;
+ numa_node_to_cpus(node, cpumask);
+ while (i != prev_cpu) {
+ if (numa_bitmask_isbitset(cpumask, i)) {
+ cur_cpu[node] = i;
+ return i;
+ }
+ i = (i+1) % nr_cpus;
+ }
+ return (-1);
+}
+
+static void set_attr_to_cpu(pthread_attr_t *attr, int cpu)
+{
+ cpu_set_t cpuset;
+
+ CPU_ZERO(&cpuset);
+ CPU_SET(cpu, &cpuset);
+ pthread_attr_setaffinity_np(attr, sizeof(cpuset), &cpuset);
+}
+
+int bench_mem_mmap(int argc, const char **argv,
+ const char *prefix __maybe_unused)
+{
+ int i, itr = 0;
+ char *m, *r;
+ struct timeval tv_start, tv_end, tv_diff;
+ double sum = 0.0, min = -1.0, max = 0.0, tput_total = 0.0;
+ double mean = 0, sdv = 0, tsq = 0.0;
+ pthread_t tid;
+ pthread_attr_t attr;
+ u64 addr;
+
+ argc = parse_options(argc, argv, options,
+ bench_mem_mmap_usage, 0);
+
+ nodemask = numa_get_run_node_mask();
+ nr_nodes = numa_max_node() + 1;
+ nr_cpus = numa_num_configured_cpus();
+ cur_node = 0;
+ cur_cpu = (int *) malloc(nr_nodes * sizeof(int));
+ if (cur_cpu == NULL) {
+ fprintf(stderr, "Not enough memory to set up benchmark\n");
+ }
+ for (i = 0; i < nr_nodes; ++i)
+ cur_cpu[i] = 0;
+
+ cpumask = numa_allocate_cpumask();
+ if ((s64)len <= 0) {
+ fprintf(stderr, "Invalid length:%s\n", length_str);
+ return 1;
+ }
+
+ m = (char *) malloc(CACHELINE_SIZE * (threads+1));
+ addr = (u64) m;
+ if (m == NULL) {
+ fprintf(stderr, "Not enough memory to store results\n");
+ return 1;
+ }
+ results = (unsigned long long **) malloc(sizeof(unsigned long long *)
+ * threads);
+ if (results == NULL) {
+ fprintf(stderr, "Not enough memory to store results\n");
+ free (m);
+ return 1;
+ }
+ r = (char *) ((addr + CACHELINE_SIZE - 1) & ~(CACHELINE_SIZE - 1));
+ for (i = 0; i < threads; i++)
+ results[i] = (unsigned long long *) &r[i * CACHELINE_SIZE];
+
+ for (i = 0; i < threads; i++) {
+ int cpu;
+
+ pthread_attr_init(&attr);
+ cur_node = get_next_node(cur_node);
+ cpu = get_next_cpu(cur_node);
+ if (cur_node < 0 || cpu < 0) {
+ fprintf(stderr, "Cannot set thread to cpu. \n");
+ return 1;
+ }
+ set_attr_to_cpu(&attr, cpu);
+ pthread_create(&tid, &attr, do_mmap, results[i]);
+ pthread_attr_destroy(&attr);
+ }
+
+ if (bench_format == BENCH_FORMAT_DEFAULT) {
+ printf("# Repeatedly memory map %s bytes and touch %llu bytes"
+ " for %d sec with %d threads ...\n", length_str,
+ (unsigned long long) len/stride, runtime, threads);
+ printf("# Warming up ");
+ fflush(stdout);
+ }
+
+ while (1) {
+ double elapsed, tput;
+
+ sleep(1);
+ BUG_ON(gettimeofday(&tv_start, NULL));
+ do_cnt = true;
+ sleep(runtime);
+ do_cnt = false;
+ BUG_ON(gettimeofday(&tv_end, NULL));
+ timersub(&tv_end, &tv_start, &tv_diff);
+ elapsed = timeval2double(&tv_diff);
+
+ sum = 0.0;
+ max = 0.0;
+ if (bench_format == BENCH_FORMAT_DEFAULT) {
+ if (itr < warmup)
+ printf(".");
+ else if (itr == warmup)
+ printf("\n\n");
+ fflush(stdout);
+ }
+
+ for (i = 0; i < threads; i++) {
+ unsigned long long val = *(results[i]);
+
+ *(results[i]) = 0;
+ tput = val / elapsed;
+ if (i == 0)
+ min = tput;
+ else if (tput < min)
+ min = tput;
+
+ if (tput > max)
+ max = tput;
+
+ sum += tput;
+ }
+
+ if (itr++ > warmup) {
+ tput_total += sum;
+ tsq += (double) (sum*sum);
+ }
+
+ if (verbose && (itr > warmup))
+ printf("iteration:%d %10lf (mmap/unmap per sec)"
+ " %10lf (MB accessed per sec)\n",
+ (itr-warmup), sum, sum*len/(stride*1024*1024));
+
+ if (itr > (iterations + warmup)) {
+ mean = tput_total/iterations;
+ sdv = sqrt((tsq - mean*mean*iterations)/iterations);
+ /* convert to percentage */
+ sdv = 100.0*(sdv/mean);
+ break;
+ }
+ }
+
+ switch (bench_format) {
+ case BENCH_FORMAT_DEFAULT:
+ printf(" %d threads %10lf (mmap/munmap per sec),"
+ " access %10lf (MB/sec) (std dev +/- %-10lf %%)\n",
+ threads, mean, mean*len/(stride*1024*1024), sdv);
+ break;
+ case BENCH_FORMAT_SIMPLE:
+ printf(" %d %-10lf %-10lf %-10lf \n", threads, mean,
+ mean*len/(stride*1024*1024), sdv);
+ break;
+ default:
+ /* reaching this means there's some disaster: */
+ die("unknown format: %d\n", bench_format);
+ break;
+ }
+
+ free (m);
+ free (results);
+ return 0;
+}
diff --git a/tools/perf/builtin-bench.c b/tools/perf/builtin-bench.c
index 77298bf..7cd4218 100644
--- a/tools/perf/builtin-bench.c
+++ b/tools/perf/builtin-bench.c
@@ -64,6 +64,9 @@ static struct bench_suite mem_suites[] = {
{ "memcpy",
"Simple memory copy in various ways",
bench_mem_memcpy },
+ { "mmap",
+ "Simple memory map + memory set in various ways",
+ bench_mem_mmap },
{ "memset",
"Simple memory set in various ways",
bench_mem_memset },
--
1.7.11.7



--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/