#include <cassert>
#include <cerrno>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <libpmem.h>
#include <sys/mman.h>
#include <unistd.h>
#include "benchmark.hpp"
#define FLUSH_ALIGN 64
#define MAX_OFFSET (FLUSH_ALIGN - 1)
struct pmem_bench;
typedef size_t (*offset_fn)(struct pmem_bench *pmb, uint64_t index);
struct pmem_args {
char *operation;
size_t src_off;
size_t dest_off;
size_t chunk_size;
char *src_mode;
char *dest_mode;
bool memcpy;
bool persist;
};
struct pmem_bench {
unsigned *rand_offsets;
size_t n_rand_offsets;
size_t fsize;
size_t bsize;
unsigned char *buf;
unsigned char *pmem_addr;
unsigned char *src_addr;
unsigned char *dest_addr;
struct pmem_args *pargs;
offset_fn func_src;
offset_fn func_dest;
int (*func_op)(void *dest, void *source, size_t len);
};
enum operation_type { OP_TYPE_UNKNOWN, OP_TYPE_READ, OP_TYPE_WRITE };
enum operation_mode {
OP_MODE_UNKNOWN,
OP_MODE_STAT,
OP_MODE_SEQ,
OP_MODE_RAND
};
static enum operation_type
parse_op_type(const char *arg)
{
if (strcmp(arg, "read") == 0)
return OP_TYPE_READ;
else if (strcmp(arg, "write") == 0)
return OP_TYPE_WRITE;
else
return OP_TYPE_UNKNOWN;
}
static enum operation_mode
parse_op_mode(const char *arg)
{
if (strcmp(arg, "stat") == 0)
return OP_MODE_STAT;
else if (strcmp(arg, "seq") == 0)
return OP_MODE_SEQ;
else if (strcmp(arg, "rand") == 0)
return OP_MODE_RAND;
else
return OP_MODE_UNKNOWN;
}
static uint64_t
mode_seq(struct pmem_bench *pmb, uint64_t index)
{
return index;
}
static uint64_t
mode_stat(struct pmem_bench *pmb, uint64_t index)
{
return 0;
}
static uint64_t
mode_rand(struct pmem_bench *pmb, uint64_t index)
{
assert(index < pmb->n_rand_offsets);
return pmb->rand_offsets[index];
}
static offset_fn
assign_mode_func(char *option)
{
enum operation_mode op_mode = parse_op_mode(option);
switch (op_mode) {
case OP_MODE_STAT:
return mode_stat;
case OP_MODE_SEQ:
return mode_seq;
case OP_MODE_RAND:
return mode_rand;
default:
return NULL;
}
}
static int
libc_memcpy(void *dest, void *source, size_t len)
{
memcpy(dest, source, len);
pmem_flush(dest, len);
return 0;
}
static int
libc_memcpy_persist(void *dest, void *source, size_t len)
{
memcpy(dest, source, len);
pmem_persist(dest, len);
return 0;
}
static int
libpmem_memcpy_nodrain(void *dest, void *source, size_t len)
{
pmem_memcpy_nodrain(dest, source, len);
return 0;
}
static int
libpmem_memcpy_persist(void *dest, void *source, size_t len)
{
pmem_memcpy_persist(dest, source, len);
return 0;
}
static int
assign_size(struct pmem_bench *pmb, struct benchmark_args *args,
enum operation_type *op_type)
{
*op_type = parse_op_type(pmb->pargs->operation);
if (*op_type == OP_TYPE_UNKNOWN) {
fprintf(stderr, "Invalid operation argument '%s'",
pmb->pargs->operation);
return -1;
}
enum operation_mode op_mode_src = parse_op_mode(pmb->pargs->src_mode);
if (op_mode_src == OP_MODE_UNKNOWN) {
fprintf(stderr, "Invalid source mode argument '%s'",
pmb->pargs->src_mode);
return -1;
}
enum operation_mode op_mode_dest = parse_op_mode(pmb->pargs->dest_mode);
if (op_mode_dest == OP_MODE_UNKNOWN) {
fprintf(stderr, "Invalid destination mode argument '%s'",
pmb->pargs->dest_mode);
return -1;
}
size_t large = args->n_ops_per_thread * pmb->pargs->chunk_size *
args->n_threads;
size_t little = pmb->pargs->chunk_size;
if (*op_type == OP_TYPE_WRITE) {
pmb->bsize = op_mode_src == OP_MODE_STAT ? little : large;
pmb->fsize = op_mode_dest == OP_MODE_STAT ? little : large;
if (pmb->pargs->src_off != 0)
pmb->bsize += MAX_OFFSET;
if (pmb->pargs->dest_off != 0)
pmb->fsize += MAX_OFFSET;
} else {
pmb->fsize = op_mode_src == OP_MODE_STAT ? little : large;
pmb->bsize = op_mode_dest == OP_MODE_STAT ? little : large;
if (pmb->pargs->src_off != 0)
pmb->fsize += MAX_OFFSET;
if (pmb->pargs->dest_off != 0)
pmb->bsize += MAX_OFFSET;
}
return 0;
}
static int
pmem_memcpy_init(struct benchmark *bench, struct benchmark_args *args)
{
assert(bench != NULL);
assert(args != NULL);
int ret = 0;
struct pmem_bench *pmb =
(struct pmem_bench *)malloc(sizeof(struct pmem_bench));
assert(pmb != NULL);
pmb->pargs = (struct pmem_args *)args->opts;
assert(pmb->pargs != NULL);
pmb->pargs->chunk_size = args->dsize;
enum operation_type op_type;
if (assign_size(pmb, args, &op_type) != 0) {
ret = -1;
goto err_free_pmb;
}
pmb->buf =
(unsigned char *)util_aligned_malloc(FLUSH_ALIGN, pmb->bsize);
if (pmb->buf == NULL) {
perror("posix_memalign");
ret = -1;
goto err_free_pmb;
}
pmb->n_rand_offsets = args->n_ops_per_thread * args->n_threads;
assert(pmb->n_rand_offsets != 0);
pmb->rand_offsets = (unsigned *)malloc(pmb->n_rand_offsets *
sizeof(*pmb->rand_offsets));
if (pmb->rand_offsets == NULL) {
perror("malloc");
ret = -1;
goto err_free_pmb;
}
for (size_t i = 0; i < pmb->n_rand_offsets; ++i)
pmb->rand_offsets[i] = rand() % args->n_ops_per_thread;
if ((pmb->pmem_addr = (unsigned char *)pmem_map_file(
args->fname, pmb->fsize, PMEM_FILE_CREATE | PMEM_FILE_EXCL,
args->fmode, NULL, NULL)) == NULL) {
perror(args->fname);
ret = -1;
goto err_free_buf;
}
if (op_type == OP_TYPE_READ) {
pmb->src_addr = pmb->pmem_addr;
pmb->dest_addr = pmb->buf;
} else {
pmb->src_addr = pmb->buf;
pmb->dest_addr = pmb->pmem_addr;
}
if ((pmb->func_src = assign_mode_func(pmb->pargs->src_mode)) == NULL) {
fprintf(stderr, "wrong src_mode parameter -- '%s'",
pmb->pargs->src_mode);
ret = -1;
goto err_unmap;
}
if ((pmb->func_dest = assign_mode_func(pmb->pargs->dest_mode)) ==
NULL) {
fprintf(stderr, "wrong dest_mode parameter -- '%s'",
pmb->pargs->dest_mode);
ret = -1;
goto err_unmap;
}
if (pmb->pargs->memcpy) {
pmb->func_op =
pmb->pargs->persist ? libc_memcpy_persist : libc_memcpy;
} else {
pmb->func_op = pmb->pargs->persist ? libpmem_memcpy_persist
: libpmem_memcpy_nodrain;
}
pmembench_set_priv(bench, pmb);
return 0;
err_unmap:
pmem_unmap(pmb->pmem_addr, pmb->fsize);
err_free_buf:
util_aligned_free(pmb->buf);
err_free_pmb:
free(pmb);
return ret;
}
static int
pmem_memcpy_operation(struct benchmark *bench, struct operation_info *info)
{
struct pmem_bench *pmb = (struct pmem_bench *)pmembench_get_priv(bench);
size_t src_index = info->args->n_ops_per_thread * info->worker->index +
pmb->func_src(pmb, info->index);
size_t dest_index = info->args->n_ops_per_thread * info->worker->index +
pmb->func_dest(pmb, info->index);
void *source = pmb->src_addr + src_index * pmb->pargs->chunk_size +
pmb->pargs->src_off;
void *dest = pmb->dest_addr + dest_index * pmb->pargs->chunk_size +
pmb->pargs->dest_off;
size_t len = pmb->pargs->chunk_size;
pmb->func_op(dest, source, len);
return 0;
}
static int
pmem_memcpy_exit(struct benchmark *bench, struct benchmark_args *args)
{
struct pmem_bench *pmb = (struct pmem_bench *)pmembench_get_priv(bench);
munmap(pmb->pmem_addr, pmb->fsize);
util_aligned_free(pmb->buf);
free(pmb->rand_offsets);
free(pmb);
return 0;
}
static struct benchmark_clo pmem_memcpy_clo[7];
static struct benchmark_info pmem_memcpy;
CONSTRUCTOR(pmem_memcpy_costructor)
void
pmem_memcpy_costructor(void)
{
pmem_memcpy_clo[0].opt_short = 'o';
pmem_memcpy_clo[0].opt_long = "operation";
pmem_memcpy_clo[0].descr = "Operation type - write, read";
pmem_memcpy_clo[0].type = CLO_TYPE_STR;
pmem_memcpy_clo[0].off = clo_field_offset(struct pmem_args, operation);
pmem_memcpy_clo[0].def = "write";
pmem_memcpy_clo[1].opt_short = 'S';
pmem_memcpy_clo[1].opt_long = "src-offset";
pmem_memcpy_clo[1].descr = "Source cache line alignment"
" offset";
pmem_memcpy_clo[1].type = CLO_TYPE_UINT;
pmem_memcpy_clo[1].off = clo_field_offset(struct pmem_args, src_off);
pmem_memcpy_clo[1].def = "0";
pmem_memcpy_clo[1].type_uint.size =
clo_field_size(struct pmem_args, src_off);
pmem_memcpy_clo[1].type_uint.base = CLO_INT_BASE_DEC;
pmem_memcpy_clo[1].type_uint.min = 0;
pmem_memcpy_clo[1].type_uint.max = MAX_OFFSET;
pmem_memcpy_clo[2].opt_short = 'D';
pmem_memcpy_clo[2].opt_long = "dest-offset";
pmem_memcpy_clo[2].descr = "Destination cache line "
"alignment offset";
pmem_memcpy_clo[2].type = CLO_TYPE_UINT;
pmem_memcpy_clo[2].off = clo_field_offset(struct pmem_args, dest_off);
pmem_memcpy_clo[2].def = "0";
pmem_memcpy_clo[2].type_uint.size =
clo_field_size(struct pmem_args, dest_off);
pmem_memcpy_clo[2].type_uint.base = CLO_INT_BASE_DEC;
pmem_memcpy_clo[2].type_uint.min = 0;
pmem_memcpy_clo[2].type_uint.max = MAX_OFFSET;
pmem_memcpy_clo[3].opt_short = 0;
pmem_memcpy_clo[3].opt_long = "src-mode";
pmem_memcpy_clo[3].descr = "Source reading mode";
pmem_memcpy_clo[3].type = CLO_TYPE_STR;
pmem_memcpy_clo[3].off = clo_field_offset(struct pmem_args, src_mode);
pmem_memcpy_clo[3].def = "seq";
pmem_memcpy_clo[4].opt_short = 0;
pmem_memcpy_clo[4].opt_long = "dest-mode";
pmem_memcpy_clo[4].descr = "Destination writing mode";
pmem_memcpy_clo[4].type = CLO_TYPE_STR;
pmem_memcpy_clo[4].off = clo_field_offset(struct pmem_args, dest_mode);
pmem_memcpy_clo[4].def = "seq";
pmem_memcpy_clo[5].opt_short = 'm';
pmem_memcpy_clo[5].opt_long = "libc-memcpy";
pmem_memcpy_clo[5].descr = "Use libc memcpy()";
pmem_memcpy_clo[5].type = CLO_TYPE_FLAG;
pmem_memcpy_clo[5].off = clo_field_offset(struct pmem_args, memcpy);
pmem_memcpy_clo[5].def = "false";
pmem_memcpy_clo[6].opt_short = 'p';
pmem_memcpy_clo[6].opt_long = "persist";
pmem_memcpy_clo[6].descr = "Use pmem_persist()";
pmem_memcpy_clo[6].type = CLO_TYPE_FLAG;
pmem_memcpy_clo[6].off = clo_field_offset(struct pmem_args, persist);
pmem_memcpy_clo[6].def = "true";
pmem_memcpy.name = "pmem_memcpy";
pmem_memcpy.brief = "Benchmark for"
"pmem_memcpy_persist() and "
"pmem_memcpy_nodrain()"
"operations";
pmem_memcpy.init = pmem_memcpy_init;
pmem_memcpy.exit = pmem_memcpy_exit;
pmem_memcpy.multithread = true;
pmem_memcpy.multiops = true;
pmem_memcpy.operation = pmem_memcpy_operation;
pmem_memcpy.measure_time = true;
pmem_memcpy.clos = pmem_memcpy_clo;
pmem_memcpy.nclos = ARRAY_SIZE(pmem_memcpy_clo);
pmem_memcpy.opts_size = sizeof(struct pmem_args);
pmem_memcpy.rm_file = true;
pmem_memcpy.allow_poolset = false;
REGISTER_BENCHMARK(pmem_memcpy);
};