376 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * mem-memcpy.c4 *5 * Simple memcpy() and memset() benchmarks6 *7 * Written by Hitoshi Mitake <mitake@dcl.info.waseda.ac.jp>8 */9 10#include "debug.h"11#include "../perf-sys.h"12#include <subcmd/parse-options.h>13#include "../util/header.h"14#include "../util/cloexec.h"15#include "../util/string2.h"16#include "bench.h"17#include "mem-memcpy-arch.h"18#include "mem-memset-arch.h"19 20#include <stdio.h>21#include <stdlib.h>22#include <string.h>23#include <unistd.h>24#include <sys/time.h>25#include <errno.h>26#include <linux/time64.h>27#include <linux/zalloc.h>28 29#define K 102430 31static const char *size_str = "1MB";32static const char *function_str = "all";33static int nr_loops = 1;34static bool use_cycles;35static int cycles_fd;36 37static const struct option options[] = {38 OPT_STRING('s', "size", &size_str, "1MB",39 "Specify the size of the memory buffers. "40 "Available units: B, KB, MB, GB and TB (case insensitive)"),41 42 OPT_STRING('f', "function", &function_str, "all",43 "Specify the function to run, \"all\" runs all available functions, \"help\" lists them"),44 45 OPT_INTEGER('l', "nr_loops", &nr_loops,46 "Specify the number of loops to run. (default: 1)"),47 48 OPT_BOOLEAN('c', "cycles", &use_cycles,49 "Use a cycles event instead of gettimeofday() to measure performance"),50 51 OPT_END()52};53 54typedef void *(*memcpy_t)(void *, const void *, size_t);55typedef void *(*memset_t)(void *, int, size_t);56 57struct function {58 const char *name;59 const char *desc;60 union {61 memcpy_t memcpy;62 memset_t memset;63 } fn;64};65 66static struct perf_event_attr cycle_attr = {67 .type = PERF_TYPE_HARDWARE,68 .config = PERF_COUNT_HW_CPU_CYCLES69};70 71static int init_cycles(void)72{73 cycles_fd = sys_perf_event_open(&cycle_attr, getpid(), -1, -1, perf_event_open_cloexec_flag());74 75 if (cycles_fd < 0 && errno == ENOSYS) {76 pr_debug("No CONFIG_PERF_EVENTS=y kernel support configured?\n");77 return -1;78 }79 80 return cycles_fd;81}82 83static u64 get_cycles(void)84{85 int ret;86 u64 clk;87 88 ret = read(cycles_fd, &clk, sizeof(u64));89 BUG_ON(ret != sizeof(u64));90 91 return clk;92}93 94static double timeval2double(struct timeval *ts)95{96 return (double)ts->tv_sec + (double)ts->tv_usec / (double)USEC_PER_SEC;97}98 99#define print_bps(x) do { \100 if (x < K) \101 printf(" %14lf bytes/sec\n", x); \102 else if (x < K * K) \103 printf(" %14lfd KB/sec\n", x / K); \104 else if (x < K * K * K) \105 printf(" %14lf MB/sec\n", x / K / K); \106 else \107 printf(" %14lf GB/sec\n", x / K / K / K); \108 } while (0)109 110struct bench_mem_info {111 const struct function *functions;112 u64 (*do_cycles)(const struct function *r, size_t size, void *src, void *dst);113 double (*do_gettimeofday)(const struct function *r, size_t size, void *src, void *dst);114 const char *const *usage;115 bool alloc_src;116};117 118static void __bench_mem_function(struct bench_mem_info *info, int r_idx, size_t size, double size_total)119{120 const struct function *r = &info->functions[r_idx];121 double result_bps = 0.0;122 u64 result_cycles = 0;123 void *src = NULL, *dst = zalloc(size);124 125 printf("# function '%s' (%s)\n", r->name, r->desc);126 127 if (dst == NULL)128 goto out_alloc_failed;129 130 if (info->alloc_src) {131 src = zalloc(size);132 if (src == NULL)133 goto out_alloc_failed;134 }135 136 if (bench_format == BENCH_FORMAT_DEFAULT)137 printf("# Copying %s bytes ...\n\n", size_str);138 139 if (use_cycles) {140 result_cycles = info->do_cycles(r, size, src, dst);141 } else {142 result_bps = info->do_gettimeofday(r, size, src, dst);143 }144 145 switch (bench_format) {146 case BENCH_FORMAT_DEFAULT:147 if (use_cycles) {148 printf(" %14lf cycles/byte\n", (double)result_cycles/size_total);149 } else {150 print_bps(result_bps);151 }152 break;153 154 case BENCH_FORMAT_SIMPLE:155 if (use_cycles) {156 printf("%lf\n", (double)result_cycles/size_total);157 } else {158 printf("%lf\n", result_bps);159 }160 break;161 162 default:163 BUG_ON(1);164 break;165 }166 167out_free:168 free(src);169 free(dst);170 return;171out_alloc_failed:172 printf("# Memory allocation failed - maybe size (%s) is too large?\n", size_str);173 goto out_free;174}175 176static int bench_mem_common(int argc, const char **argv, struct bench_mem_info *info)177{178 int i;179 size_t size;180 double size_total;181 182 argc = parse_options(argc, argv, options, info->usage, 0);183 184 if (use_cycles) {185 i = init_cycles();186 if (i < 0) {187 fprintf(stderr, "Failed to open cycles counter\n");188 return i;189 }190 }191 192 size = (size_t)perf_atoll((char *)size_str);193 size_total = (double)size * nr_loops;194 195 if ((s64)size <= 0) {196 fprintf(stderr, "Invalid size:%s\n", size_str);197 return 1;198 }199 200 if (!strncmp(function_str, "all", 3)) {201 for (i = 0; info->functions[i].name; i++)202 __bench_mem_function(info, i, size, size_total);203 return 0;204 }205 206 for (i = 0; info->functions[i].name; i++) {207 if (!strcmp(info->functions[i].name, function_str))208 break;209 }210 if (!info->functions[i].name) {211 if (strcmp(function_str, "help") && strcmp(function_str, "h"))212 printf("Unknown function: %s\n", function_str);213 printf("Available functions:\n");214 for (i = 0; info->functions[i].name; i++) {215 printf("\t%s ... %s\n",216 info->functions[i].name, info->functions[i].desc);217 }218 return 1;219 }220 221 __bench_mem_function(info, i, size, size_total);222 223 return 0;224}225 226static void memcpy_prefault(memcpy_t fn, size_t size, void *src, void *dst)227{228 /* Make sure to always prefault zero pages even if MMAP_THRESH is crossed: */229 memset(src, 0, size);230 231 /*232 * We prefault the freshly allocated memory range here,233 * to not measure page fault overhead:234 */235 fn(dst, src, size);236}237 238static u64 do_memcpy_cycles(const struct function *r, size_t size, void *src, void *dst)239{240 u64 cycle_start = 0ULL, cycle_end = 0ULL;241 memcpy_t fn = r->fn.memcpy;242 int i;243 244 memcpy_prefault(fn, size, src, dst);245 246 cycle_start = get_cycles();247 for (i = 0; i < nr_loops; ++i)248 fn(dst, src, size);249 cycle_end = get_cycles();250 251 return cycle_end - cycle_start;252}253 254static double do_memcpy_gettimeofday(const struct function *r, size_t size, void *src, void *dst)255{256 struct timeval tv_start, tv_end, tv_diff;257 memcpy_t fn = r->fn.memcpy;258 int i;259 260 memcpy_prefault(fn, size, src, dst);261 262 BUG_ON(gettimeofday(&tv_start, NULL));263 for (i = 0; i < nr_loops; ++i)264 fn(dst, src, size);265 BUG_ON(gettimeofday(&tv_end, NULL));266 267 timersub(&tv_end, &tv_start, &tv_diff);268 269 return (double)(((double)size * nr_loops) / timeval2double(&tv_diff));270}271 272struct function memcpy_functions[] = {273 { .name = "default",274 .desc = "Default memcpy() provided by glibc",275 .fn.memcpy = memcpy },276 277#ifdef HAVE_ARCH_X86_64_SUPPORT278# define MEMCPY_FN(_fn, _name, _desc) {.name = _name, .desc = _desc, .fn.memcpy = _fn},279# include "mem-memcpy-x86-64-asm-def.h"280# undef MEMCPY_FN281#endif282 283 { .name = NULL, }284};285 286static const char * const bench_mem_memcpy_usage[] = {287 "perf bench mem memcpy <options>",288 NULL289};290 291int bench_mem_memcpy(int argc, const char **argv)292{293 struct bench_mem_info info = {294 .functions = memcpy_functions,295 .do_cycles = do_memcpy_cycles,296 .do_gettimeofday = do_memcpy_gettimeofday,297 .usage = bench_mem_memcpy_usage,298 .alloc_src = true,299 };300 301 return bench_mem_common(argc, argv, &info);302}303 304static u64 do_memset_cycles(const struct function *r, size_t size, void *src __maybe_unused, void *dst)305{306 u64 cycle_start = 0ULL, cycle_end = 0ULL;307 memset_t fn = r->fn.memset;308 int i;309 310 /*311 * We prefault the freshly allocated memory range here,312 * to not measure page fault overhead:313 */314 fn(dst, -1, size);315 316 cycle_start = get_cycles();317 for (i = 0; i < nr_loops; ++i)318 fn(dst, i, size);319 cycle_end = get_cycles();320 321 return cycle_end - cycle_start;322}323 324static double do_memset_gettimeofday(const struct function *r, size_t size, void *src __maybe_unused, void *dst)325{326 struct timeval tv_start, tv_end, tv_diff;327 memset_t fn = r->fn.memset;328 int i;329 330 /*331 * We prefault the freshly allocated memory range here,332 * to not measure page fault overhead:333 */334 fn(dst, -1, size);335 336 BUG_ON(gettimeofday(&tv_start, NULL));337 for (i = 0; i < nr_loops; ++i)338 fn(dst, i, size);339 BUG_ON(gettimeofday(&tv_end, NULL));340 341 timersub(&tv_end, &tv_start, &tv_diff);342 343 return (double)(((double)size * nr_loops) / timeval2double(&tv_diff));344}345 346static const char * const bench_mem_memset_usage[] = {347 "perf bench mem memset <options>",348 NULL349};350 351static const struct function memset_functions[] = {352 { .name = "default",353 .desc = "Default memset() provided by glibc",354 .fn.memset = memset },355 356#ifdef HAVE_ARCH_X86_64_SUPPORT357# define MEMSET_FN(_fn, _name, _desc) { .name = _name, .desc = _desc, .fn.memset = _fn },358# include "mem-memset-x86-64-asm-def.h"359# undef MEMSET_FN360#endif361 362 { .name = NULL, }363};364 365int bench_mem_memset(int argc, const char **argv)366{367 struct bench_mem_info info = {368 .functions = memset_functions,369 .do_cycles = do_memset_cycles,370 .do_gettimeofday = do_memset_gettimeofday,371 .usage = bench_mem_memset_usage,372 };373 374 return bench_mem_common(argc, argv, &info);375}376