├── .gitignore ├── benchmark ├── config │ ├── ycsb_prefill.conf │ ├── ycsb_1090_uniform.conf │ ├── ycsb_1090_zipf.conf │ ├── ycsb_5050_uniform.conf │ ├── ycsb_5050_zipf.conf │ └── rocksdb.conf ├── fixtures │ ├── ycsb_common.hpp │ ├── ycsb_common.cpp │ ├── common_fixture.hpp │ ├── tbb_fixture.hpp │ ├── utree_fixture.hpp │ ├── rocksdb_fixture.hpp │ ├── crl_fixture.hpp │ ├── common_fixture.cpp │ ├── pmem_kv_fixture.hpp │ ├── viper_fixture.hpp │ └── cceh_fixture.hpp ├── generate_ycsb.sh ├── benchmark.cpp ├── convert_ycsb.py ├── recovery_bm.cpp ├── update_bm.cpp ├── key_value_size_bm.cpp ├── variable_size_bm.cpp ├── benchmark.hpp ├── reclaim_bm.cpp ├── ycsb_bm.cpp ├── all_ops_benchmark.cpp └── CMakeLists.txt ├── eval ├── matplot_conf.json ├── requirements.txt ├── memory.ipynb ├── update.ipynb ├── common.py ├── access_pattern.ipynb ├── reclaim.ipynb ├── kv_size.ipynb ├── var_size.ipynb ├── viper_versions.ipynb ├── all_ops.ipynb ├── ycsb.ipynb ├── breakdown.ipynb └── breakdown_half.ipynb ├── playground.cpp ├── CMakeLists.txt ├── include └── viper │ └── hash.hpp ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | cmake-*/ 2 | results/ 3 | .ipynb_checkpoints/ 4 | venv/ 5 | charts/ 6 | __pycache__/ -------------------------------------------------------------------------------- /benchmark/config/ycsb_prefill.conf: -------------------------------------------------------------------------------- 1 | recordcount=100000000 2 | workload=site.ycsb.workloads.CoreWorkload 3 | 4 | fieldcount=1 5 | fieldlength=200 6 | -------------------------------------------------------------------------------- /benchmark/config/ycsb_1090_uniform.conf: -------------------------------------------------------------------------------- 1 | operationcount=50000000 2 | workload=site.ycsb.workloads.CoreWorkload 3 | 4 | readallfields=false 5 | 6 | readproportion=0.1 7 | updateproportion=0 8 | scanproportion=0 9 | insertproportion=0.9 10 | 11 | requestdistribution=uniform 12 | -------------------------------------------------------------------------------- /benchmark/config/ycsb_1090_zipf.conf: -------------------------------------------------------------------------------- 1 | operationcount=50000000 2 | workload=site.ycsb.workloads.CoreWorkload 3 | 4 | readallfields=false 5 | 6 | readproportion=0.1 7 | updateproportion=0 8 | scanproportion=0 9 | insertproportion=0.9 10 | 11 | requestdistribution=zipfian 12 | -------------------------------------------------------------------------------- /benchmark/config/ycsb_5050_uniform.conf: -------------------------------------------------------------------------------- 1 | operationcount=50000000 2 | workload=site.ycsb.workloads.CoreWorkload 3 | 4 | readallfields=false 5 | 6 | readproportion=0.5 7 | updateproportion=0 8 | scanproportion=0 9 | insertproportion=0.5 10 | 11 | requestdistribution=uniform 12 | -------------------------------------------------------------------------------- /benchmark/config/ycsb_5050_zipf.conf: -------------------------------------------------------------------------------- 1 | operationcount=50000000 2 | workload=site.ycsb.workloads.CoreWorkload 3 | 4 | readallfields=false 5 | 6 | readproportion=0.5 7 | updateproportion=0 8 | scanproportion=0 9 | insertproportion=0.5 10 | 11 | requestdistribution=zipfian 12 | -------------------------------------------------------------------------------- /eval/matplot_conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "figure.autolayout": true, 3 | 4 | "xtick.labelsize": 16, 5 | "ytick.labelsize": 16, 6 | 7 | "axes.titlesize" : 16, 8 | "axes.labelsize" : 14, 9 | "axes.linewidth": 2, 10 | 11 | "lines.linewidth" : 3, 12 | "lines.markersize" : 6, 13 | 14 | "legend.fontsize": 14, 15 | 16 | "font.size": 15, 17 | "font.family": "STIXGeneral", 18 | "mathtext.fontset": "stix", 19 | "pgf.rcfonts" : false 20 | } 21 | -------------------------------------------------------------------------------- /benchmark/fixtures/ycsb_common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "../benchmark.hpp" 6 | 7 | namespace viper::kv_bm::ycsb { 8 | 9 | struct Record { 10 | enum Op : uint32_t { INSERT = 0, GET = 1, UPDATE = 2 }; 11 | // This will be overwritten by the file read. 12 | const Op op = INSERT; 13 | const KeyType8 key; 14 | const ValueType200 value; 15 | }; 16 | 17 | void read_workload_file(const std::filesystem::path wl_file, std::vector* data); 18 | 19 | } 20 | 21 | 22 | -------------------------------------------------------------------------------- /playground.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "viper/viper.hpp" 4 | 5 | int main(int argc, char** argv) { 6 | const size_t initial_size = 1073741824; // 1 GiB 7 | auto viper_db = viper::Viper::create("/mnt/pmem2/viper", initial_size); 8 | 9 | // To modify records in Viper, you need to use a Viper Client. 10 | auto v_client = viper_db->get_client(); 11 | 12 | for (uint64_t key = 0; key < 10; ++key) { 13 | const uint64_t value = key + 10; 14 | v_client.put(key, value); 15 | } 16 | 17 | for (uint64_t key = 0; key < 11; ++key) { 18 | uint64_t value; 19 | const bool found = v_client.get(key, &value); 20 | if (found) { 21 | std::cout << "Record: " << key << " --> " << value << std::endl; 22 | } else { 23 | std::cout << "No record found for key: " << key << std::endl; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /benchmark/fixtures/ycsb_common.cpp: -------------------------------------------------------------------------------- 1 | #include "ycsb_common.hpp" 2 | 3 | #include 4 | #include 5 | 6 | namespace viper::kv_bm::ycsb { 7 | 8 | void read_workload_file(const std::filesystem::path wl_file, std::vector* data) { 9 | std::ifstream ifs{wl_file, std::ios::binary | std::ios::ate}; 10 | 11 | if (!ifs) { 12 | throw std::runtime_error("Cannot open " + wl_file.string() + ": " + std::strerror(errno)); 13 | } 14 | 15 | auto end = ifs.tellg(); 16 | ifs.seekg(0, std::ios::beg); 17 | auto size = std::size_t(end - ifs.tellg()); 18 | 19 | if (size == 0) { 20 | throw std::runtime_error(wl_file.string() + " is empty."); 21 | } 22 | 23 | assert(size % sizeof(Record) == 0); 24 | const uint64_t num_records = size / sizeof(Record); 25 | data->resize(num_records); 26 | 27 | if (!ifs.read((char*) data->data(), size)) { 28 | throw std::runtime_error("Error reading from " + wl_file.string() + ": " + std::strerror(errno)); 29 | } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /benchmark/generate_ycsb.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | #BASE_DIR="/hpi/fs00/home/lawrence.benson/clion/viper1/benchmark" 6 | BASE_DIR="/scratch/lawrence.benson/code/benchmark" 7 | PREFILL_CONF="${BASE_DIR}/config/ycsb_prefill.conf" 8 | DATA_DIR="/mnt/nvme2/viper" 9 | 10 | CONFIGS=( "5050_uniform" "5050_zipf" "1090_uniform" "1090_zipf" ) 11 | 12 | #cd "/hpi/fs00/home/lawrence.benson/ycsb" 13 | cd "/scratch/lawrence.benson/ycsb" 14 | 15 | echo "GENERATING PREFILL DATA" 16 | ./bin/ycsb load basic -P ${PREFILL_CONF} -s > "${DATA_DIR}/raw_prefill.dat" 17 | 18 | echo "GENERATING YCSB DATA" 19 | for config in "${CONFIGS[@]}" 20 | do 21 | echo "GENERATING ${config}..." 22 | ./bin/ycsb run basic -P ${PREFILL_CONF} \ 23 | -P "${BASE_DIR}/config/ycsb_${config}.conf" \ 24 | -s > "${DATA_DIR}/raw_ycsb_wl_${config}.dat" 25 | done 26 | 27 | 28 | cd "${BASE_DIR}" 29 | echo "CONVERTING DATA TO BINARY FORMAT" 30 | 31 | python3 convert_ycsb.py "${DATA_DIR}/raw_prefill.dat" "${DATA_DIR}/ycsb_prefill.dat" 32 | 33 | for config in "${CONFIGS[@]}" 34 | do 35 | echo "CONVERTING: ${config}..." 36 | python3 convert_ycsb.py "${DATA_DIR}/raw_ycsb_wl_${config}.dat" "${DATA_DIR}/ycsb_wl_${config}.dat" 37 | rm "${DATA_DIR}/raw_ycsb_wl_${config}.dat" 38 | done 39 | -------------------------------------------------------------------------------- /eval/requirements.txt: -------------------------------------------------------------------------------- 1 | appnope==0.1.2 2 | argon2-cffi==20.1.0 3 | async-generator==1.10 4 | attrs==20.3.0 5 | backcall==0.2.0 6 | bleach==3.2.1 7 | cffi==1.14.4 8 | cycler==0.10.0 9 | decorator==4.4.2 10 | defusedxml==0.6.0 11 | entrypoints==0.3 12 | ipykernel==5.4.3 13 | ipython==7.19.0 14 | ipython-genutils==0.2.0 15 | ipywidgets==7.6.3 16 | jedi==0.18.0 17 | Jinja2==2.11.2 18 | jsonschema==3.2.0 19 | jupyter==1.0.0 20 | jupyter-client==6.1.11 21 | jupyter-console==6.2.0 22 | jupyter-core==4.7.0 23 | jupyterlab-pygments==0.1.2 24 | jupyterlab-widgets==1.0.0 25 | kiwisolver==1.3.1 26 | MarkupSafe==1.1.1 27 | matplotlib==3.3.3 28 | mistune==0.8.4 29 | nbclient==0.5.1 30 | nbconvert==6.0.7 31 | nbformat==5.1.2 32 | nest-asyncio==1.4.3 33 | notebook==6.2.0 34 | numpy==1.19.5 35 | packaging==20.8 36 | pandocfilters==1.4.3 37 | parso==0.8.1 38 | pexpect==4.8.0 39 | pickleshare==0.7.5 40 | Pillow==8.1.0 41 | prometheus-client==0.9.0 42 | prompt-toolkit==3.0.11 43 | ptyprocess==0.7.0 44 | pycparser==2.20 45 | Pygments==2.7.4 46 | pyparsing==2.4.7 47 | pyrsistent==0.17.3 48 | python-dateutil==2.8.1 49 | pyzmq==21.0.1 50 | qtconsole==5.0.1 51 | QtPy==1.9.0 52 | Send2Trash==1.5.0 53 | six==1.15.0 54 | terminado==0.9.2 55 | testpath==0.4.4 56 | tornado==6.1 57 | traitlets==5.0.5 58 | wcwidth==0.2.5 59 | webencodings==0.5.1 60 | widgetsnbextension==3.5.1 61 | -------------------------------------------------------------------------------- /benchmark/benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "benchmark.hpp" 3 | 4 | namespace viper::kv_bm { 5 | 6 | std::string get_time_string() { 7 | auto now = std::chrono::system_clock::now(); 8 | auto in_time_t = std::chrono::system_clock::to_time_t(now); 9 | 10 | std::stringstream ss; 11 | ss << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d-%H-%M"); 12 | return ss.str(); 13 | } 14 | 15 | std::string get_output_file(const std::string& bm_name) { 16 | return std::string("--benchmark_out=") + RESULT_FILE_DIR + bm_name + "_" + get_time_string() + ".json"; 17 | } 18 | 19 | 20 | int bm_main(std::vector args) { 21 | args.push_back("--benchmark_counters_tabular=true"); 22 | 23 | std::vector cstrings; 24 | cstrings.reserve(args.size()); 25 | bool found_out_file = false; 26 | for (size_t i = 0; i < args.size(); ++i) { 27 | if (args[i].find("--benchmark_out=") != std::string::npos) { 28 | found_out_file = true; 29 | } 30 | cstrings.push_back(const_cast(args[i].c_str())); 31 | } 32 | 33 | if (!found_out_file) { 34 | std::cout << "NOT STORING RESULTS!" << std::endl; 35 | } 36 | 37 | int argc = args.size(); 38 | char** argv = &cstrings[0]; 39 | ::benchmark::Initialize(&argc, argv); 40 | if (::benchmark::ReportUnrecognizedArguments(argc, argv)) return 1; 41 | ::benchmark::RunSpecifiedBenchmarks(); 42 | return 0; 43 | } 44 | 45 | } // namespace viper::kv_bm 46 | -------------------------------------------------------------------------------- /benchmark/convert_ycsb.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import struct 4 | import codecs 5 | 6 | INSERT_RE = re.compile(r'^(INSERT|UPDATE) usertable user(\d+) \[ field0=(.{200}) .*$') 7 | READ_RE = re.compile(r'^READ usertable user(\d+) .*$') 8 | BIN_UINT64_T_FORMAT = ' 2 | 3 | #include "benchmark.hpp" 4 | #include "fixtures/viper_fixture.hpp" 5 | 6 | using namespace viper::kv_bm; 7 | 8 | using VFixture = ViperFixture; 9 | 10 | constexpr size_t RECLAIM_NUM_PREFILLS = 100'000'000; 11 | 12 | inline void bm_recovery(benchmark::State& state, VFixture& fixture) { 13 | const uint64_t num_total_prefill = RECLAIM_NUM_PREFILLS; 14 | const uint64_t recovery_threads = state.range(0); 15 | 16 | fixture.InitMap(num_total_prefill); 17 | fixture.DeInitMap(); 18 | 19 | std::unique_ptr viper; 20 | viper::ViperConfig v_config{}; 21 | v_config.num_recovery_threads = recovery_threads; 22 | 23 | set_cpu_affinity(0, 36); 24 | uint64_t rec_time_ms = 0; 25 | 26 | for (auto _ : state) { 27 | auto start = std::chrono::high_resolution_clock::now(); 28 | viper = VFixture::ViperT::open(VIPER_POOL_FILE, v_config); 29 | auto end = std::chrono::high_resolution_clock::now(); 30 | auto duration = std::chrono::duration_cast(end - start); 31 | rec_time_ms = duration.count(); 32 | } 33 | state.counters["rec_time_ms"] = rec_time_ms; 34 | } 35 | 36 | BENCHMARK_TEMPLATE2_DEFINE_F(ViperFixture, recovery, KeyType16, ValueType200)(benchmark::State& state) { \ 37 | bm_recovery(state, *this); 38 | } 39 | 40 | BENCHMARK_REGISTER_F(ViperFixture, recovery) 41 | ->Iterations(1)->Unit(BM_TIME_UNIT)->UseRealTime() 42 | ->Arg(1)->Arg(2)->Arg(4)->Arg(8) 43 | ->Arg(16)->Arg(24)->Arg(32)->Arg(36); 44 | 45 | int main(int argc, char** argv) { 46 | std::string exec_name = argv[0]; 47 | const std::string arg = get_output_file("recovery/recovery"); 48 | return bm_main({exec_name, arg}); 49 | // return bm_main({exec_name}); 50 | } 51 | -------------------------------------------------------------------------------- /include/viper/hash.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This code was taken and modified from https://github.com/DICL/CCEH, the original authors of CCEH. 3 | * 4 | * Orignial License: 5 | * Copyright (c) 2018, Sungkyunkwan University. All rights reserved. 6 | * The license is a free non-exclusive, non-transferable license to reproduce, 7 | * use, modify and display the source code version of the Software, with or 8 | * without modifications solely for non-commercial research, educational or 9 | * evaluation purposes. The license does not entitle Licensee to technical 10 | * support, telephone assistance, enhancements or updates to the Software. All 11 | * rights, title to and ownership interest in the Software, including all 12 | * intellectual property rights therein shall remain in Sungkyunkwan University. 13 | */ 14 | 15 | #pragma once 16 | 17 | #include 18 | #include 19 | 20 | namespace viper::cceh { 21 | 22 | inline size_t standard(const void* _ptr, size_t _len, 23 | size_t _seed = static_cast(0xc70f6907UL)) { 24 | return std::_Hash_bytes(_ptr, _len, _seed); 25 | } 26 | 27 | inline size_t murmur2(const void* key, size_t len, size_t seed = 0xc70f6907UL) { 28 | const unsigned int m = 0x5bd1e995; 29 | const int r = 24; 30 | unsigned int h = seed ^len; 31 | const unsigned char* data = (const unsigned char*) key; 32 | 33 | while (len >= 4) { 34 | unsigned int k = *(unsigned int*) data; 35 | k *= m; 36 | k ^= k >> r; 37 | k *= m; 38 | h *= m; 39 | h ^= k; 40 | data += 4; 41 | len -= 4; 42 | } 43 | 44 | switch (len) { 45 | case 3: h ^= data[2] << 16; 46 | case 2: h ^= data[1] << 8; 47 | case 1: h ^= data[0]; 48 | h *= m; 49 | }; 50 | 51 | h ^= h >> 13; 52 | h *= m; 53 | h ^= h >> 15; 54 | return h; 55 | } 56 | 57 | static size_t 58 | (* hash_funcs[2])(const void* key, size_t len, size_t seed) = { 59 | standard, 60 | murmur2 61 | }; 62 | 63 | inline size_t h(const void* key, size_t len, size_t seed = 0xc70697UL) { 64 | return hash_funcs[0](key, len, seed); 65 | } 66 | 67 | } // namespace viper::cceh 68 | -------------------------------------------------------------------------------- /eval/memory.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%load_ext autoreload\n", 10 | "%autoreload 2\n", 11 | "from common import *" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "CONSUMPTION = {\n", 21 | " \"FASTER\" : (15.5, 8),\n", 22 | " \"Viper\" : (21.2, 2.35), \n", 23 | " \"Dash\" : (23.8 + 2.1, 0),\n", 24 | " \"µTree\" : (23.8, 9.0),\n", 25 | " \"PmemKV\" : (51.7, 0),\n", 26 | " \"CrlStore\" : (1.1 + 39.8, 27.8),\n", 27 | "}\n", 28 | "\n", 29 | "fixtures, sizes = zip(*CONSUMPTION.items())\n", 30 | "pmem, dram = zip(*sizes)\n", 31 | "\n", 32 | "plt.rcParams.update({'hatch.color': 'white', 'hatch.linewidth': 2})\n", 33 | "ind = np.arange(len(fixtures))\n", 34 | "\n", 35 | "fig, ax = plt.subplots(1, 1, figsize=(3.5, 2))\n", 36 | "\n", 37 | "ax.bar(ind, pmem, label='PMem', color=PMEM_COLOR, lw=2, hatch='')\n", 38 | "ax.bar(ind, dram, bottom=pmem, label='DRAM', color=DRAM_COLOR, lw=2, hatch='')\n", 39 | "\n", 40 | "# ax.set_ylim(0, 55)\n", 41 | "y_ticks = range(0, 71, 10)\n", 42 | "ax.set_yticks(y_ticks)\n", 43 | "ax.set_yticklabels(['0', '', '20', '', '40', '', '60', ''], fontsize=20)\n", 44 | "ax.set_xticks(ind)\n", 45 | "ax.set_xticklabels(fixtures, fontsize=16, rotation=45)\n", 46 | "\n", 47 | "ax.set_ylabel(\"Total Memory (GB)\", fontsize=18)\n", 48 | "ax.yaxis.set_label_coords(-0.2, 0.28)\n", 49 | "\n", 50 | "handles, labels = ax.get_legend_handles_labels()\n", 51 | "fig.legend(handles[::-1], labels[::-1], bbox_to_anchor=(0.32, 0.95), loc='upper center',\n", 52 | " ncol=1, frameon=True, fontsize=20, facecolor='white', framealpha=0, edgecolor='white',\n", 53 | " columnspacing=0.5, handletextpad=0.3, labelspacing=0.1, handlelength=1.4)\n", 54 | "\n", 55 | "ax.set_axisbelow(True)\n", 56 | "ax.grid(axis='y', which='both')\n", 57 | "hide_border(ax)\n", 58 | "\n", 59 | "fig.savefig('charts/memory.pdf', bbox_inches='tight')\n", 60 | "fig.savefig('charts/memory.svg', bbox_inches='tight')" 61 | ] 62 | } 63 | ], 64 | "metadata": { 65 | "kernelspec": { 66 | "display_name": "Python 3", 67 | "language": "python", 68 | "name": "python3" 69 | }, 70 | "language_info": { 71 | "codemirror_mode": { 72 | "name": "ipython", 73 | "version": 3 74 | }, 75 | "file_extension": ".py", 76 | "mimetype": "text/x-python", 77 | "name": "python", 78 | "nbconvert_exporter": "python", 79 | "pygments_lexer": "ipython3", 80 | "version": "3.9.1" 81 | } 82 | }, 83 | "nbformat": 4, 84 | "nbformat_minor": 4 85 | } 86 | -------------------------------------------------------------------------------- /benchmark/update_bm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "benchmark.hpp" 4 | #include "fixtures/viper_fixture.hpp" 5 | 6 | using namespace viper::kv_bm; 7 | 8 | constexpr size_t UPDATE_NUM_REPETITIONS = 1; 9 | constexpr size_t UPDATE_NUM_PREFILLS = 100'000'000; 10 | constexpr size_t UPDATE_NUM_INSERTS = 50'000'000; 11 | 12 | #define GENERAL_ARGS \ 13 | ->Repetitions(UPDATE_NUM_REPETITIONS) \ 14 | ->Iterations(1) \ 15 | ->Unit(BM_TIME_UNIT) \ 16 | ->UseRealTime() \ 17 | ->ThreadRange(1, NUM_MAX_THREADS) \ 18 | ->Threads(24) 19 | 20 | #define DEFINE_BM_INTERNAL(fixture, method) \ 21 | BENCHMARK_TEMPLATE2_DEFINE_F(fixture, method, \ 22 | KeyType16, ValueType200)(benchmark::State& state) { \ 23 | bm_##method(state, *this); \ 24 | } \ 25 | BENCHMARK_REGISTER_F(fixture, method) GENERAL_ARGS 26 | 27 | #define DEFINE_BM(fixture) \ 28 | DEFINE_BM_INTERNAL(fixture, in_place_update) \ 29 | ->Args({UPDATE_NUM_PREFILLS, UPDATE_NUM_INSERTS}); \ 30 | DEFINE_BM_INTERNAL(fixture, get_update) \ 31 | ->Args({UPDATE_NUM_PREFILLS, UPDATE_NUM_INSERTS}) 32 | 33 | inline void bm_in_place_update(benchmark::State& state, BaseFixture& fixture) { 34 | const uint64_t num_total_prefill = state.range(0); 35 | const uint64_t num_total_inserts = state.range(1); 36 | 37 | set_cpu_affinity(state.thread_index); 38 | 39 | if (is_init_thread(state)) { 40 | fixture.InitMap(num_total_prefill); 41 | } 42 | 43 | const uint64_t num_updates_per_thread = num_total_inserts / state.threads; 44 | const uint64_t start_idx = 0; 45 | const uint64_t end_idx = num_total_prefill - state.threads; 46 | 47 | for (auto _ : state) { 48 | fixture.setup_and_update(start_idx, end_idx, num_updates_per_thread); 49 | 50 | } 51 | 52 | state.SetItemsProcessed(num_updates_per_thread); 53 | 54 | if (is_init_thread(state)) { 55 | fixture.DeInitMap(); 56 | } 57 | } 58 | 59 | template 60 | inline void bm_get_update(benchmark::State& state, ViperFixture& fixture) { 61 | const uint64_t num_total_prefill = state.range(0); 62 | const uint64_t num_total_finds = state.range(1); 63 | 64 | set_cpu_affinity(state.thread_index); 65 | 66 | if (is_init_thread(state)) { 67 | fixture.InitMap(num_total_prefill); 68 | } 69 | 70 | const uint64_t num_updates_per_thread = num_total_finds / state.threads; 71 | const uint64_t start_idx = 0; 72 | const uint64_t end_idx = num_total_prefill - state.threads; 73 | 74 | size_t update_counter = 0; 75 | for (auto _ : state) { 76 | update_counter = fixture.setup_and_get_update(start_idx, end_idx, num_updates_per_thread); 77 | } 78 | 79 | state.SetItemsProcessed(num_updates_per_thread); 80 | 81 | if (is_init_thread(state)) { 82 | fixture.DeInitMap(); 83 | } 84 | 85 | BaseFixture::log_find_count(state, update_counter, num_updates_per_thread); 86 | } 87 | 88 | DEFINE_BM(ViperFixture); 89 | 90 | int main(int argc, char** argv) { 91 | std::string exec_name = argv[0]; 92 | const std::string arg = get_output_file("update/update"); 93 | return bm_main({exec_name, arg}); 94 | // return bm_main({exec_name}); 95 | } 96 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 HPI Data Engineering Systems 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | 24 | Licenses of Dependencies: 25 | ====================================== 26 | 27 | CCEH 28 | ---- 29 | Copyright (c) 2018, Sungkyunkwan University. All rights reserved. 30 | 31 | The license is a free non-exclusive, non-transferable license to reproduce, 32 | use, modify and display the source code version of the Software, with or 33 | without modifications solely for non-commercial research, educational or 34 | evaluation purposes. The license does not entitle Licensee to technical 35 | support, telephone assistance, enhancements or updates to the Software. All 36 | rights, title to and ownership interest in the Software, including all 37 | intellectual property rights therein shall remain in Sungkyunkwan University. 38 | 39 | ====================================== 40 | 41 | concurrentqueue 42 | --------------- 43 | Copyright (c) 2013-2016, Cameron Desrochers. All rights reserved. 44 | 45 | Redistribution and use in source and binary forms, with or without modification, 46 | are permitted provided that the following conditions are met: 47 | - Redistributions of source code must retain the above copyright notice, 48 | this list of conditions and the following disclaimer. 49 | - Redistributions in binary form must reproduce the above copyright notice, 50 | this list of conditions and the following disclaimer in the documentation 51 | and/or other materials provided with the distribution. 52 | 53 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 54 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 55 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 56 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 57 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 58 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 59 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 60 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 61 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 62 | POSSIBILITY OF SUCH DAMAGE. 63 | 64 | ====================================== 65 | -------------------------------------------------------------------------------- /eval/update.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%load_ext autoreload\n", 10 | "%autoreload 2\n", 11 | "from common import *" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "RESULT_JSON = \"/Users/law/repos/viper/results/update/update_bm_revision.json\"" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "from collections import defaultdict\n", 30 | "runs = defaultdict(list)\n", 31 | "\n", 32 | "BMS = get_all_runs(RESULT_JSON)\n", 33 | "IN_PLACE = 'in_place'\n", 34 | "COPY_WRITE = 'get_update'\n", 35 | "\n", 36 | "for bm in BMS:\n", 37 | " if IN_PLACE in bm['name']:\n", 38 | " runs[IN_PLACE].append(bm)\n", 39 | " elif COPY_WRITE in bm['name']:\n", 40 | " runs[COPY_WRITE].append(bm)\n", 41 | " else:\n", 42 | " raise RuntimeError(f\"Unknown benchmark type {bm['name']}\")\n", 43 | "\n", 44 | "runs[IN_PLACE].sort(key=lambda x: x['threads'])\n", 45 | "runs[COPY_WRITE].sort(key=lambda x: x['threads'])\n", 46 | "\n", 47 | "# pprint(runs)" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "metadata": {}, 54 | "outputs": [], 55 | "source": [ 56 | "in_place_w_threads = [(run['threads'], run['items_per_second'] / MILLION) for run in runs[IN_PLACE]]\n", 57 | "copy_write = [run['items_per_second'] / MILLION for run in runs[COPY_WRITE]]\n", 58 | "\n", 59 | "threads, in_place = zip(*in_place_w_threads)\n", 60 | "\n", 61 | "fig, ax = plt.subplots(1, 1, figsize=(4, 3))\n", 62 | "\n", 63 | "ax.plot(threads, in_place, label=\"In-Place\", ls='-', color=PMEM_COLOR, lw=3)\n", 64 | "ax.plot(threads, copy_write, label=\"CoW\", ls='--', color=DRAM_COLOR, lw=3)\n", 65 | "ax.set_yticks(range(0, 16, 5))\n", 66 | "\n", 67 | "ax.set_ylabel(\"Throughput\\n(Mops/s)\", fontsize=18)\n", 68 | "ax.set_xlabel(\"# Threads\", fontsize=18)\n", 69 | "ax.legend(frameon=False, loc='upper left', fontsize=18,\n", 70 | " columnspacing=1.3, handletextpad=0.3, labelspacing=0.1,\n", 71 | " bbox_to_anchor=(-0.05, 1.25))\n", 72 | "\n", 73 | "for tick in ax.xaxis.get_major_ticks():\n", 74 | " tick.label.set_fontsize(18)\n", 75 | "for tick in ax.yaxis.get_major_ticks():\n", 76 | " tick.label.set_fontsize(18)\n", 77 | "\n", 78 | "# ax.yaxis.set_label_coords(-0.12, 0.45)\n", 79 | "\n", 80 | "ax.set_axisbelow(True)\n", 81 | "ax.grid(axis='y', which='major')\n", 82 | "hide_border(ax)\n", 83 | "plt.tight_layout()\n", 84 | "fig.savefig('charts/update.pdf', bbox_inches='tight')\n", 85 | "fig.savefig('charts/update.svg', bbox_inches='tight')" 86 | ] 87 | } 88 | ], 89 | "metadata": { 90 | "kernelspec": { 91 | "display_name": "Python 3", 92 | "language": "python", 93 | "name": "python3" 94 | }, 95 | "language_info": { 96 | "codemirror_mode": { 97 | "name": "ipython", 98 | "version": 3 99 | }, 100 | "file_extension": ".py", 101 | "mimetype": "text/x-python", 102 | "name": "python", 103 | "nbconvert_exporter": "python", 104 | "pygments_lexer": "ipython3", 105 | "version": "3.9.1" 106 | } 107 | }, 108 | "nbformat": 4, 109 | "nbformat_minor": 4 110 | } 111 | -------------------------------------------------------------------------------- /eval/common.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | import json 3 | from matplotlib import rcParams 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | from pprint import pprint 7 | import re 8 | 9 | # rcParams.update(json.loads(open("matplot_conf.json").read())) 10 | 11 | MILLION = 1_000_000 12 | 13 | class Style: 14 | def __init__(self, color, marker, marker_size, hatch): 15 | self.color = color 16 | self.marker = marker 17 | self.marker_size = marker_size 18 | self.hatch = hatch 19 | 20 | 21 | ROCKS = ('PmemRocksDb', "PMem-RocksDB") 22 | PMEMKV = ('PmemKV', "pmemkv") 23 | VIPER = ('Viper', 'Viper') 24 | DRAM_MAP = ('DramMap', 'TBB Map') 25 | DASH = ('Dash', 'Dash') 26 | UTREE = ('UTree', 'µTree') 27 | CRL_STORE = ('Crl', 'CrlStore') 28 | HYBRID_FASTER = ('PmemHybridFaster', 'FASTER') 29 | NVM_FASTER = ('NvmFaster', 'FASTER-PMem') 30 | CCEH = ('Cceh', 'CCEH') 31 | VIPER_PMEM = ('PmemVip', 'PMem') 32 | VIPER_DRAM = ('DramVip', 'DRAM') 33 | VIPER_UNALIGNED = ('UnalignedVip', 'Unaligned') 34 | 35 | ALL_FIXTURES = [ 36 | VIPER, ROCKS, PMEMKV, HYBRID_FASTER, DASH, 37 | # VIPER_PMEM, 38 | # DRAM_MAP, CCEH, NVM_FASTER, 39 | ] 40 | 41 | ALL_BM_TYPES = [ 42 | 'insert', 'get', 'update', 'delete', 43 | '5050_uniform', '1090_uniform', '5050_zipf', '1090_zipf', 44 | ] 45 | 46 | # red blue green grey purple turquoise 47 | COLORS = ['#990000', '#000099', '#006600', '#404040', '#990099', '#666600'] 48 | MARKERS = [('X', 12), ("s", 8), ("d", 10), ("o", 10), ("^", 10), ('v', 10)] 49 | 50 | # THREAD_TO_MARKER = { 51 | # 1: 'x', 2: '>', 4: 's', 6: '<', 8: 'o', 16: '^', 18: 'v', 24: '.', 32: 'P', 36: 'D' 52 | # } 53 | # THREAD_TO_COLOR = { 54 | # 1: '#000000', 2: '#009999', 4: '#4C0099', 6: '#999900', 55 | # 8: '#990000', 16: '#CC6600', 18: '#0080FF', 24: '#009900', 56 | # 32: '#CCCC00', 36: '#404040', 57 | # } 58 | DRAM_COLOR = '#0080FF' 59 | PMEM_COLOR = '#303030' 60 | 61 | STYLES = { 62 | VIPER[0]: Style('#990000', '.', 12, 'x'), 63 | PMEMKV[0]: Style('#009900', 'D', 10, ''), 64 | HYBRID_FASTER[0]: Style('#990099', '^', 10, '\\\\'), 65 | DASH[0]: Style(PMEM_COLOR, 'o', 10, '//'), 66 | ROCKS[0]: Style('#000099', 'v', 8, ''), 67 | UTREE[0]: Style('#999900', '>', 8, ''), 68 | CRL_STORE[0]: Style(DRAM_COLOR, 's', 8, ''), 69 | 70 | VIPER_PMEM[0]: Style(PMEM_COLOR, 'o', 10, 'o'), 71 | VIPER_DRAM[0]: Style(DRAM_COLOR, 's', 8, '//'), 72 | VIPER_UNALIGNED[0]: Style('#990000', 's', 8, '//'), 73 | CCEH[0]: Style('#000099', 's', 8, '//'), 74 | # NVM_FASTER[0]: Style('#666600', 'v', 10, '-'), 75 | # DRAM_MAP[0]: Style('#404040', 'o', 10, '//'), 76 | } 77 | 78 | def get_bm_type(bm_type_str): 79 | for t in ALL_BM_TYPES: 80 | if t in bm_type_str: 81 | return t 82 | raise RuntimeError(f"Unknown bm_type: {bm_type_str}") 83 | 84 | def get_benchmarks(bms, fixtures=ALL_FIXTURES, types=ALL_BM_TYPES): 85 | runs = defaultdict(list) 86 | for bm in bms: 87 | for (fixture, _) in fixtures: 88 | bm_type = get_bm_type(bm['name']) 89 | if fixture in bm['name']: 90 | runs[(fixture, bm_type)].append(bm) 91 | break 92 | 93 | for fixture, _ in fixtures: 94 | for bm_type in types: 95 | runs[(fixture, bm_type)].sort(key=lambda x: x['threads']) 96 | 97 | return runs 98 | 99 | 100 | def get_all_runs(result_json_file): 101 | with open(result_json_file) as rj_f: 102 | results_raw = json.loads(rj_f.read()) 103 | return results_raw["benchmarks"] 104 | 105 | 106 | def hide_border(ax, show_left=False): 107 | ax.spines['top'].set_visible(False) 108 | ax.spines['right'].set_visible(False) 109 | ax.spines['bottom'].set_visible(True) 110 | ax.spines['left'].set_visible(show_left) 111 | -------------------------------------------------------------------------------- /eval/access_pattern.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%load_ext autoreload\n", 10 | "%autoreload 2\n", 11 | "from common import *" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "RESULT_JSON = \"/Users/law/repos/viper/results/access_pattern/all_access_pattern.json\"" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "import json\n", 30 | "\n", 31 | "results_raw = json.loads(open(RESULT_JSON).read())\n", 32 | "assert results_raw[\"context\"][\"library_build_type\"] != \"debug\"\n", 33 | "\n", 34 | "BENCHMARKS = results_raw[\"benchmarks\"]\n", 35 | "# print(BENCHMARKS)" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": null, 41 | "metadata": { 42 | "scrolled": true 43 | }, 44 | "outputs": [], 45 | "source": [ 46 | "from collections import defaultdict\n", 47 | "runs = defaultdict(list)\n", 48 | "\n", 49 | "DIMM = 'dimm'\n", 50 | "BLOCK = 'block'\n", 51 | "\n", 52 | "for bm in BENCHMARKS:\n", 53 | " if DIMM in bm['name']:\n", 54 | " runs[DIMM].append(bm)\n", 55 | " elif BLOCK in bm['name']:\n", 56 | " runs[BLOCK].append(bm)\n", 57 | " else:\n", 58 | " raise RuntimeError(f\"Unknown benchmark type {bm['name']}\")\n", 59 | "\n", 60 | "runs[DIMM].sort(key=lambda x: x['threads'])\n", 61 | "runs[BLOCK].sort(key=lambda x: x['threads'])\n", 62 | "\n", 63 | "pprint(runs)" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": null, 69 | "metadata": {}, 70 | "outputs": [], 71 | "source": [ 72 | "dimm_runs = [run['items_per_second'] / MILLION for run in runs[DIMM]]\n", 73 | "block_runs = [run['items_per_second'] / MILLION for run in runs[BLOCK]]\n", 74 | "\n", 75 | "fig, ax = plt.subplots(1, 1, figsize=(4, 3))\n", 76 | "\n", 77 | "num_threads = list(range(1, 37, ))\n", 78 | "ax.plot(num_threads, dimm_runs, label=\"DIMM\", ls='-.', color=COLORS[3])\n", 79 | "ax.plot(num_threads, block_runs, label=\"VBlock\", ls='-', color=COLORS[0])\n", 80 | "ax.set_yticks(range(1, 12))\n", 81 | "ax.yaxis.set_major_locator(MultipleLocator(2))\n", 82 | "\n", 83 | "ax.set_ylabel(\"Throughput (Mops/s)\", fontsize=18)\n", 84 | "ax.set_xlabel(\"# Threads\", fontsize=18)\n", 85 | "ax.legend(frameon=True, loc='upper left', fontsize=18,\n", 86 | " columnspacing=1.3, handletextpad=0.3, labelspacing=0.1,\n", 87 | " bbox_to_anchor=(-0.03, 1.13))\n", 88 | "\n", 89 | "for tick in ax.xaxis.get_major_ticks():\n", 90 | " tick.label.set_fontsize(18)\n", 91 | "for tick in ax.yaxis.get_major_ticks():\n", 92 | " tick.label.set_fontsize(18)\n", 93 | "\n", 94 | "ax.yaxis.set_label_coords(-0.12, 0.45)\n", 95 | "\n", 96 | "ax.set_axisbelow(True)\n", 97 | "ax.grid(axis='y', which='major')\n", 98 | "fig.savefig('charts/access_pattern.pdf', bbox_inches='tight')\n", 99 | "fig.savefig('charts/access_pattern.svg', bbox_inches='tight')" 100 | ] 101 | } 102 | ], 103 | "metadata": { 104 | "kernelspec": { 105 | "display_name": "Python 3", 106 | "language": "python", 107 | "name": "python3" 108 | }, 109 | "language_info": { 110 | "codemirror_mode": { 111 | "name": "ipython", 112 | "version": 3 113 | }, 114 | "file_extension": ".py", 115 | "mimetype": "text/x-python", 116 | "name": "python", 117 | "nbconvert_exporter": "python", 118 | "pygments_lexer": "ipython3", 119 | "version": "3.7.7" 120 | } 121 | }, 122 | "nbformat": 4, 123 | "nbformat_minor": 4 124 | } 125 | -------------------------------------------------------------------------------- /benchmark/key_value_size_bm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "benchmark.hpp" 4 | #include "fixtures/viper_fixture.hpp" 5 | #include "fixtures/cceh_fixture.hpp" 6 | #include "fixtures/faster_fixture.hpp" 7 | #include "fixtures/pmem_kv_fixture.hpp" 8 | #include "fixtures/crl_fixture.hpp" 9 | #include "fixtures/dash_fixture.hpp" 10 | 11 | using namespace viper::kv_bm; 12 | 13 | constexpr size_t KV_SIZES_NUM_REPETITIONS = 1; 14 | constexpr double KV_SIZES_SCALE_FACTOR = 1.0; 15 | constexpr size_t KV_SIZES_PREFILL_SIZE = 20 * (1000l * 1000 * 1000) * KV_SIZES_SCALE_FACTOR; 16 | constexpr size_t KV_SIZES_INSERT_SIZE = KV_SIZES_PREFILL_SIZE / 2; 17 | constexpr size_t KV_SIZES_NUM_FINDS = 50'000'000 * KV_SIZES_SCALE_FACTOR; 18 | 19 | #define GENERAL_ARGS \ 20 | Repetitions(KV_SIZES_NUM_REPETITIONS) \ 21 | ->Iterations(1) \ 22 | ->Unit(BM_TIME_UNIT) \ 23 | ->UseRealTime() \ 24 | ->Threads(36) 25 | 26 | #define DEFINE_BM_INTERNAL(fixture, method, KS, VS) \ 27 | BENCHMARK_TEMPLATE2_DEFINE_F(fixture, method ##_ ##KS ##_ ##VS, \ 28 | KeyType##KS, ValueType##VS)(benchmark::State& state) { \ 29 | bm_##method(state, *this); \ 30 | } \ 31 | BENCHMARK_REGISTER_F(fixture, method ##_ ##KS ##_ ##VS)->GENERAL_ARGS 32 | 33 | #define DEFINE_BM(fixture, KS, VS) \ 34 | DEFINE_BM_INTERNAL(fixture, insert, KS, VS) \ 35 | ->Args({KV_SIZES_PREFILL_SIZE / (KS + VS), KV_SIZES_INSERT_SIZE / (KS + VS)}); \ 36 | DEFINE_BM_INTERNAL(fixture, get, KS, VS) \ 37 | ->Args({KV_SIZES_PREFILL_SIZE / (KS + VS), KV_SIZES_NUM_FINDS}) 38 | 39 | #define DEFINE_ALL_BMS(fixture) \ 40 | DEFINE_BM(fixture, 8, 8); \ 41 | DEFINE_BM(fixture, 16, 200); \ 42 | DEFINE_BM(fixture, 32, 500); \ 43 | DEFINE_BM(fixture, 100, 900); \ 44 | 45 | 46 | inline void bm_insert(benchmark::State& state, BaseFixture& fixture) { 47 | const uint64_t num_total_prefill = state.range(0); 48 | const uint64_t num_total_inserts = state.range(1); 49 | 50 | set_cpu_affinity(state.thread_index); 51 | 52 | if (is_init_thread(state)) { 53 | fixture.InitMap(num_total_prefill); 54 | } 55 | 56 | const uint64_t num_inserts_per_thread = num_total_inserts / state.threads; 57 | const uint64_t start_idx = (state.thread_index * num_inserts_per_thread) + num_total_prefill; 58 | const uint64_t end_idx = start_idx + num_inserts_per_thread; 59 | 60 | for (auto _ : state) { 61 | fixture.setup_and_insert(start_idx, end_idx); 62 | 63 | } 64 | 65 | state.SetItemsProcessed(num_inserts_per_thread); 66 | 67 | if (is_init_thread(state)) { 68 | fixture.DeInitMap(); 69 | } 70 | } 71 | 72 | inline void bm_get(benchmark::State& state, BaseFixture& fixture) { 73 | const uint64_t num_total_prefill = state.range(0); 74 | const uint64_t num_total_finds = state.range(1); 75 | 76 | set_cpu_affinity(state.thread_index); 77 | 78 | if (is_init_thread(state)) { 79 | fixture.InitMap(num_total_prefill); 80 | } 81 | 82 | const uint64_t num_finds_per_thread = num_total_finds / state.threads; 83 | const uint64_t start_idx = 0; 84 | const uint64_t end_idx = num_total_prefill - state.threads; 85 | 86 | for (auto _ : state) { 87 | fixture.setup_and_find(start_idx, end_idx, num_finds_per_thread); 88 | } 89 | 90 | state.SetItemsProcessed(num_finds_per_thread); 91 | 92 | if (is_init_thread(state)) { 93 | fixture.DeInitMap(); 94 | } 95 | } 96 | 97 | DEFINE_ALL_BMS(ViperFixture); 98 | //DEFINE_ALL_BMS(CrlFixture); 99 | //DEFINE_ALL_BMS(DashFixture); 100 | //DEFINE_ALL_BMS(PmemHybridFasterFixture); 101 | //DEFINE_ALL_BMS(PmemKVFixture); 102 | 103 | 104 | int main(int argc, char** argv) { 105 | std::string exec_name = argv[0]; 106 | const std::string arg = get_output_file("kv_size/kv_size"); 107 | return bm_main({exec_name, arg}); 108 | // return bm_main({exec_name}); 109 | } 110 | -------------------------------------------------------------------------------- /benchmark/variable_size_bm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "benchmark.hpp" 4 | #include "fixtures/viper_fixture.hpp" 5 | #include "fixtures/faster_fixture.hpp" 6 | #include "fixtures/pmem_kv_fixture.hpp" 7 | #include "fixtures/crl_fixture.hpp" 8 | #include "fixtures/dash_fixture.hpp" 9 | 10 | using namespace viper::kv_bm; 11 | 12 | constexpr size_t VAR_SIZES_NUM_REPETITIONS = 1; 13 | constexpr size_t VAR_SIZES_PREFILL_SIZE = 20 * (1024ul * 1024 * 1024); 14 | constexpr size_t VAR_SIZES_INSERT_SIZE = VAR_SIZES_PREFILL_SIZE / 2; 15 | constexpr size_t VAR_SIZES_NUM_FINDS = 50'000'000; 16 | 17 | #define GENERAL_ARGS \ 18 | Repetitions(VAR_SIZES_NUM_REPETITIONS) \ 19 | ->Iterations(1) \ 20 | ->Unit(BM_TIME_UNIT) \ 21 | ->UseRealTime() \ 22 | ->ThreadRange(1, NUM_MAX_THREADS) \ 23 | ->Threads(24) 24 | 25 | #define DEFINE_BM_INTERNAL(fixture, method, KS, VS) \ 26 | BENCHMARK_TEMPLATE_DEFINE_F(fixture, method ##_ ##KS ##_ ##VS, \ 27 | std::string, std::string)(benchmark::State& state) { \ 28 | bm_##method(state, *this, KS, VS); \ 29 | } \ 30 | BENCHMARK_REGISTER_F(fixture, method ##_ ##KS ##_ ##VS)->GENERAL_ARGS 31 | 32 | #define DEFINE_BM(fixture, KS, VS) \ 33 | DEFINE_BM_INTERNAL(fixture, insert, KS, VS) \ 34 | ->Args({VAR_SIZES_PREFILL_SIZE / (KS + VS), VAR_SIZES_INSERT_SIZE / (KS + VS)}); \ 35 | DEFINE_BM_INTERNAL(fixture, get, KS, VS) \ 36 | ->Args({VAR_SIZES_PREFILL_SIZE / (KS + VS), VAR_SIZES_NUM_FINDS}) 37 | 38 | 39 | #define DEFINE_ALL_BMS(fixture) \ 40 | DEFINE_BM(fixture, 16, 200) 41 | 42 | void bm_insert(benchmark::State& state, BaseFixture& fixture, size_t key_size, size_t value_size) { 43 | const uint64_t num_total_prefill = state.range(0); 44 | const uint64_t num_total_inserts = state.range(1); 45 | 46 | set_cpu_affinity(state.thread_index); 47 | 48 | if (is_init_thread(state)) { 49 | const size_t num_total_strings = num_total_prefill + num_total_inserts; 50 | fixture.generate_strings(num_total_strings, key_size, value_size); 51 | fixture.InitMap(num_total_prefill); 52 | } 53 | 54 | const uint64_t num_inserts_per_thread = num_total_inserts / state.threads; 55 | const uint64_t start_idx = (state.thread_index * num_inserts_per_thread) + num_total_prefill; 56 | const uint64_t end_idx = start_idx + num_inserts_per_thread; 57 | 58 | uint64_t insert_counter = 0; 59 | for (auto _ : state) { 60 | insert_counter = fixture.setup_and_insert(start_idx, end_idx); 61 | } 62 | 63 | state.SetItemsProcessed(num_inserts_per_thread); 64 | fixture.log_find_count(state, insert_counter, num_inserts_per_thread); 65 | 66 | if (is_init_thread(state)) { 67 | fixture.DeInitMap(); 68 | } 69 | } 70 | 71 | void bm_get(benchmark::State& state, BaseFixture& fixture, size_t key_size, size_t value_size) { 72 | const uint64_t num_total_prefill = state.range(0); 73 | const uint64_t num_total_finds = state.range(1); 74 | 75 | set_cpu_affinity(state.thread_index); 76 | 77 | if (is_init_thread(state)) { 78 | const size_t num_total_strings = num_total_prefill; 79 | fixture.generate_strings(num_total_strings, key_size, value_size); 80 | fixture.InitMap(num_total_prefill); 81 | } 82 | 83 | const uint64_t num_finds_per_thread = num_total_finds / state.threads; 84 | const uint64_t start_idx = 0; 85 | const uint64_t end_idx = num_total_prefill - state.threads; 86 | 87 | size_t found_counter = 0; 88 | for (auto _ : state) { 89 | found_counter = fixture.setup_and_find(start_idx, end_idx, num_finds_per_thread); 90 | } 91 | 92 | state.SetItemsProcessed(num_finds_per_thread); 93 | fixture.log_find_count(state, found_counter, num_finds_per_thread); 94 | 95 | if (is_init_thread(state)) { 96 | fixture.DeInitMap(); 97 | } 98 | } 99 | 100 | DEFINE_ALL_BMS(ViperFixture); 101 | //DEFINE_ALL_BMS(CrlFixture); 102 | //DEFINE_ALL_BMS(PmemKVFixture); 103 | //DEFINE_ALL_BMS(DashFixture); 104 | //DEFINE_ALL_BMS(PmemHybridFasterFixture); 105 | 106 | 107 | int main(int argc, char** argv) { 108 | std::string exec_name = argv[0]; 109 | const std::string arg = get_output_file("var_size/var_size"); 110 | return bm_main({exec_name, arg}); 111 | // return bm_main({exec_name}); 112 | } 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Viper: An Efficient Hybrid PMem-DRAM Key-Value Store

2 |

This repository contains the code to our VLDB '21 paper.

3 | 4 | 5 | ### Using Viper 6 | Viper is an embedded header-only key-value store for persistent memory. 7 | You can download it and include it in your application without using CMake (check out the [Downloading Viper](#downloading-viper) and [Dependencies](#dependencies) sections below). 8 | Here is a short example of Viper's interface (this is the content of `playground.cpp`). 9 | 10 | ```cpp 11 | #include 12 | #include "viper/viper.hpp" 13 | 14 | int main(int argc, char** argv) { 15 | const size_t initial_size = 1073741824; // 1 GiB 16 | auto viper_db = viper::Viper::create("/mnt/pmem2/viper", initial_size); 17 | 18 | // To modify records in Viper, you need to use a Viper Client. 19 | auto v_client = viper_db->get_client(); 20 | 21 | for (uint64_t key = 0; key < 10; ++key) { 22 | const uint64_t value = key + 10; 23 | v_client.put(key, value); 24 | } 25 | 26 | for (uint64_t key = 0; key < 11; ++key) { 27 | uint64_t value; 28 | const bool found = v_client.get(key, &value); 29 | if (found) { 30 | std::cout << "Record: " << key << " --> " << value << std::endl; 31 | } else { 32 | std::cout << "No record found for key: " << key << std::endl; 33 | } 34 | } 35 | } 36 | ``` 37 | 38 | ### Downloading Viper 39 | As Viper is header-only, you only need to download the header files and include them in your code as shown above. 40 | You do not need to use Viper's CMakeLists.txt. 41 | Just make sure you have the [dependencies](#dependencies) installed. 42 | Here is a common way to do include Viper using `FetchContent` in CMake. 43 | By default, this will fetch all dependencies. 44 | 45 | ```cmake 46 | include(FetchContent) 47 | 48 | FetchContent_Declare( 49 | viper 50 | GIT_REPOSITORY https://github.com/hpides/viper.git 51 | ) 52 | FetchContent_MakeAvailable(viper) 53 | 54 | # ... other CMake stuff 55 | 56 | # Link Viper to get the transitive dependencies 57 | target_link_libraries(your-target viper) 58 | ``` 59 | 60 | This avoids calling all the Viper CMake code, which is mainly needed for the benchmark code. 61 | Of course, you can also simply download the code from GitHub into a third_party directory or use your preferred method. 62 | Check out the CMake options for Viper at the top of [CMakeLists.txt](https://github.com/hpides/viper/blob/master/CMakeLists.txt) 63 | for more details on what to include and build. 64 | 65 | 66 | ### Dependencies 67 | Viper depends on [concurrentqueue 1.0.3](https://github.com/cameron314/concurrentqueue). 68 | As Viper is header-only, you should make sure that this dependency is available. 69 | Check out the CMake options for Viper at the top of the [CMakeLists.txt](https://github.com/hpides/viper/blob/master/CMakeLists.txt) 70 | for more details. 71 | Check out Viper's [CMakeLists.txt](https://github.com/hpides/viper/blob/c5a3707001dac131421f98a36ebf4f5309b19e35/CMakeLists.txt#L28-L36) to see an example of how to add `concurrentqueue` as a dependency. 72 | You can find the licenses of the dependencies in the [LICENSE file](https://github.com/hpides/viper/blob/master/LICENSE). 73 | 74 | ### Building the Benchmarks 75 | First off, if you want to compare your system's performance against Viper, it's probably best to include Viper in your 76 | benchmark framework instead of relying on this one. 77 | 78 | To build the benchmarks, you need to use pass: 79 | ``` 80 | -DVIPER_BUILD_BENCHMARKS=ON -DVIPER_PMDK_PATH=/path/to/pmdk -DLIBPMEMOBJ++_PATH=/path/to/libpmemobj++ 81 | ``` 82 | 83 | CMake should download and build all dependencies automatically. 84 | You can then run the individual benchmarks (executables with `_bm` suffix) in the `benchmark` directory. 85 | 86 | **NOTE**: not all benchmarks will complete by default, due to things such as out-of-memory errors. 87 | These problems are in some third party systems that I could not fix. 88 | You might need to play around with them for a bit and remove certain runs/configurations and run them manually bit-by-bit. 89 | You will also need to specify some benchmark info in the `benchmark.hpp`, such as the data directories and CPU-affinity. 90 | 91 | 92 | ### Cite Our Work 93 | If you use Viper in your work, please cite us. 94 | 95 | ```bibtex 96 | @article{benson_viper_2021, 97 | author = {Lawrence Benson and Hendrik Makait and Tilmann Rabl}, 98 | title = {Viper: An Efficient Hybrid PMem-DRAM Key-Value Store}, 99 | journal = {Proceedings of the {VLDB} Endowment}, 100 | volume = {14}, 101 | number = {9}, 102 | year = {2021}, 103 | pages = {1544--1556}, 104 | doi = {10.14778/3461535.3461543} 105 | } 106 | ``` 107 | -------------------------------------------------------------------------------- /eval/reclaim.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%load_ext autoreload\n", 10 | "%autoreload 2\n", 11 | "from common import *" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "RESULT_JSON = \"/Users/law/repos/viper/results/reclaim/reclaim_revision.json\"" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": { 27 | "scrolled": true 28 | }, 29 | "outputs": [], 30 | "source": [ 31 | "BM_TYPES = ['WRITE', 'READ']\n", 32 | "\n", 33 | "BMS = get_all_runs(RESULT_JSON)\n", 34 | "# pprint(BMS) \n", 35 | "\n", 36 | "BM_TYPE_RE = re.compile(r'.*reclaim_fixed_(.+?)/.*/threads:(\\d+)')\n", 37 | "\n", 38 | "def get_reclaim_bm_type(bm_name):\n", 39 | " match = BM_TYPE_RE.match(bm_name)\n", 40 | " if match is None:\n", 41 | " raise RuntimeError(f\"unknown bm type: {bm_name}\")\n", 42 | " return (match.group(1), int(match.group(2)) == 33)\n", 43 | "\n", 44 | "RUNS = {}\n", 45 | "\n", 46 | "for bm in BMS:\n", 47 | " reclaim_bm = get_reclaim_bm_type(bm['name'])\n", 48 | " RUNS[reclaim_bm] = bm\n", 49 | " \n", 50 | "# pprint(RUNS)" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "fig, ax = plt.subplots(1, 1, figsize=(3.5, 2.5))\n", 60 | "\n", 61 | "# Set position of bar on X axis\n", 62 | "bar_width = 0.30\n", 63 | "bar_diff = 0.02\n", 64 | "r1 = np.arange(2)\n", 65 | "r2 = [x + bar_width + bar_diff for x in r1]\n", 66 | "\n", 67 | "HATCHES = ['//', '', '\\\\\\\\', 'x']\n", 68 | "\n", 69 | "\n", 70 | "write_none = RUNS[('WRITE', False)]['items_per_second'] / MILLION\n", 71 | "write_recl = RUNS[('WRITE', True)]['items_per_second'] / MILLION\n", 72 | "read_none = RUNS[('READ', False)]['items_per_second'] / MILLION\n", 73 | "read_recl = RUNS[('READ', True)]['items_per_second'] / MILLION\n", 74 | "\n", 75 | "#ax.bar(r1[0], write_none, bar_width, label=\"None\", edgecolor=COLORS[0], lw=2, hatch='//', color='white')\n", 76 | "#ax.bar(r2[0], write_recl, bar_width, label='Reclaim', color=COLORS[3], lw=2, edgecolor=COLORS[3])\n", 77 | "#ax.bar(r1[1], read_none, bar_width, edgecolor=COLORS[0], lw=2, hatch='//', color='white')\n", 78 | "#ax.bar(r2[1], read_recl, bar_width, color=COLORS[3], lw=2, edgecolor=COLORS[3])\n", 79 | "ax.bar(r1[0], write_none, bar_width, label=\"None\", color=DRAM_COLOR)\n", 80 | "ax.bar(r2[0], write_recl, bar_width, label='Reclaim', color=PMEM_COLOR)\n", 81 | "ax.bar(r1[1], read_none, bar_width, color=DRAM_COLOR)\n", 82 | "ax.bar(r2[1], read_recl, bar_width, color=PMEM_COLOR)\n", 83 | "\n", 84 | "\n", 85 | "ax.set_xticks([r + (0.5 * bar_width) for r in range(2)])\n", 86 | "ax.set_axisbelow(True)\n", 87 | "ax.grid(axis='y', which='major')\n", 88 | "for tick in ax.xaxis.get_major_ticks():\n", 89 | " tick.label.set_fontsize(20)\n", 90 | "for tick in ax.yaxis.get_major_ticks():\n", 91 | " tick.label.set_fontsize(18)\n", 92 | "\n", 93 | "\n", 94 | "ax.set_ylabel(\"Throughput\\n(Mops/s)\", fontsize=16)\n", 95 | "ax.set_xticklabels(['PUT', 'GET'], fontsize=16)\n", 96 | "\n", 97 | "# fig.legend(loc='upper center', bbox_to_anchor=(0.48, 1.15), \n", 98 | "# ncol=2, frameon=False, fontsize=18, columnspacing=0.6, handletextpad=0.3)\n", 99 | "fig.legend(loc='upper center', bbox_to_anchor=(0.51, 0.94), \n", 100 | " ncol=1, frameon=False, fontsize=16, columnspacing=0.4, handletextpad=0.2, \n", 101 | " borderpad=0.1, labelspacing=0.1, handlelength=1.4)\n", 102 | "\n", 103 | "ax.spines['top'].set_visible(False)\n", 104 | "ax.spines['right'].set_visible(False)\n", 105 | "ax.spines['bottom'].set_visible(False)\n", 106 | "ax.spines['left'].set_visible(False)\n", 107 | "\n", 108 | "plt.tight_layout()\n", 109 | "fig.savefig('charts/reclaim.pdf', bbox_inches='tight')\n", 110 | "fig.savefig('charts/reclaim.svg', bbox_inches='tight')" 111 | ] 112 | } 113 | ], 114 | "metadata": { 115 | "kernelspec": { 116 | "display_name": "Python 3", 117 | "language": "python", 118 | "name": "python3" 119 | }, 120 | "language_info": { 121 | "codemirror_mode": { 122 | "name": "ipython", 123 | "version": 3 124 | }, 125 | "file_extension": ".py", 126 | "mimetype": "text/x-python", 127 | "name": "python", 128 | "nbconvert_exporter": "python", 129 | "pygments_lexer": "ipython3", 130 | "version": "3.9.1" 131 | } 132 | }, 133 | "nbformat": 4, 134 | "nbformat_minor": 4 135 | } 136 | -------------------------------------------------------------------------------- /benchmark/fixtures/common_fixture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "../benchmark.hpp" 16 | #include "ycsb_common.hpp" 17 | 18 | namespace viper::kv_bm { 19 | 20 | static constexpr std::array CPUS { 21 | // CPU 1 22 | 0, 1, 2, 5, 6, 9, 10, 14, 15, // NUMA NODE 0 23 | 3, 4, 7, 8, 11, 12, 13, 16, 17, // NUMA NODE 1 24 | 36, 37, 38, 41, 42, 45, 46, 50, 51, // NUMA NODE 0 25 | 39, 40, 43, 44, 47, 48, 49, 52, 53, // NUMA NODE 1 26 | // CPU 2 27 | 18, 19, 20, 23, 24, 27, 28, 32, 33, // NUMA NODE 2 28 | 21, 22, 25, 26, 29, 30, 31, 34, 35, // NUMA NODE 3 29 | 54, 55, 56, 59, 60, 63, 64, 68, 69, // NUMA NODE 2 30 | 57, 58, 61, 62, 65, 66, 67, 70, 71 // NUMA NODE 3 31 | }; 32 | 33 | bool is_init_thread(const benchmark::State& state); 34 | 35 | void set_cpu_affinity(); 36 | void set_cpu_affinity(uint16_t thread_idx); 37 | void set_cpu_affinity(uint16_t from, uint16_t to); 38 | 39 | std::string random_file(const std::filesystem::path& base_dir); 40 | 41 | using VarSizeKVs = std::pair, std::vector>; 42 | 43 | void zero_block_device(const std::string& block_dev, size_t length); 44 | 45 | class BaseFixture : public benchmark::Fixture { 46 | public: 47 | void SetUp(benchmark::State& state) override {} 48 | void TearDown(benchmark::State& state) override {} 49 | 50 | virtual void InitMap(const uint64_t num_prefill_inserts = 0, const bool re_init = true) {}; 51 | virtual void DeInitMap() {}; 52 | 53 | template 54 | void prefill_internal(size_t num_prefills, PrefillFn prefill_fn); 55 | 56 | void prefill(size_t num_prefills); 57 | virtual void prefill_ycsb(const std::vector& data); 58 | 59 | void generate_strings(size_t num_strings, size_t key_size, size_t value_size); 60 | 61 | // Benchmark methods. All pure virtual. 62 | virtual uint64_t setup_and_insert(uint64_t start_idx, uint64_t end_idx) = 0; 63 | virtual uint64_t setup_and_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates) = 0; 64 | virtual uint64_t setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) = 0; 65 | virtual uint64_t setup_and_delete(uint64_t start_idx, uint64_t end_idx, uint64_t num_deletes) = 0; 66 | 67 | virtual uint64_t run_ycsb(uint64_t start_idx, uint64_t end_idx, 68 | const std::vector& data, hdr_histogram* hdr) { 69 | throw std::runtime_error("YCSB not implemented"); 70 | } 71 | 72 | void merge_hdr(hdr_histogram* other) { 73 | std::lock_guard lock{hdr_lock_}; 74 | hdr_add(hdr_, other); 75 | } 76 | 77 | hdr_histogram* get_hdr() { return hdr_; } 78 | hdr_histogram* hdr_ = nullptr; 79 | 80 | static void log_find_count(benchmark::State& state, const uint64_t num_found, const uint64_t num_expected); 81 | 82 | protected: 83 | virtual uint64_t insert(uint64_t start_idx, uint64_t end_idx) = 0; 84 | 85 | std::mutex hdr_lock_; 86 | size_t num_util_threads_ = NUM_UTIL_THREADS; 87 | 88 | static VarSizeKVs var_size_kvs_; 89 | }; 90 | 91 | template 92 | class BasePmemFixture : public BaseFixture { 93 | public: 94 | void SetUp(benchmark::State& state) override { 95 | BaseFixture::SetUp(state); 96 | int sds_write_value = 0; 97 | pmemobj_ctl_set(NULL, "sds.at_create", &sds_write_value); 98 | 99 | { 100 | std::scoped_lock lock(pool_mutex_); 101 | if (pool_file_.empty()) { 102 | pool_file_ = random_file(DB_PMEM_DIR); 103 | // std::cout << "Working on NVM file " << pool_file_ << std::endl; 104 | pmem_pool_ = pmem::obj::pool::create(pool_file_, "", BM_POOL_SIZE, S_IRWXU); 105 | } 106 | } 107 | } 108 | 109 | // this is called and pool is closed but viper still points to something 110 | void TearDown(benchmark::State& state) override { 111 | { 112 | std::scoped_lock lock(pool_mutex_); 113 | if (!pool_file_.empty() && std::filesystem::exists(pool_file_)) { 114 | pmem_pool_.close(); 115 | if (pmempool_rm(pool_file_.c_str(), PMEMPOOL_RM_FORCE | PMEMPOOL_RM_POOLSET_LOCAL) == -1) { 116 | std::cout << pmempool_errormsg() << std::endl; 117 | } 118 | pool_file_.clear(); 119 | } 120 | } 121 | BaseFixture::TearDown(state); 122 | } 123 | 124 | protected: 125 | pmem::obj::pool pmem_pool_; 126 | std::filesystem::path pool_file_; 127 | std::mutex pool_mutex_; 128 | }; 129 | 130 | } // namespace viper::kv_bm 131 | -------------------------------------------------------------------------------- /eval/kv_size.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%load_ext autoreload\n", 10 | "%autoreload 2\n", 11 | "from common import *" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "RESULT_JSON = \"/Users/law/repos/viper/results/kv_size/all_kv_size_revision.json\"" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": { 27 | "scrolled": true 28 | }, 29 | "outputs": [], 30 | "source": [ 31 | "FIXTURES = [VIPER, DASH, HYBRID_FASTER, PMEMKV, CRL_STORE]\n", 32 | "BM_TYPES = ['insert', 'get']\n", 33 | "\n", 34 | "BMS = get_all_runs(RESULT_JSON)\n", 35 | "# pprint(BMS) \n", 36 | "\n", 37 | "KV_SIZE_RE = re.compile(r\".*KeyType(\\d+),ValueType(\\d+).*\")\n", 38 | "\n", 39 | "def get_kv_size(bm_name):\n", 40 | " match = KV_SIZE_RE.match(bm_name)\n", 41 | " if match is None:\n", 42 | " raise f\"unknown kv size: {bm_name}\"\n", 43 | " return (int(match.group(1)), int(match.group(2)))\n", 44 | "\n", 45 | "RUNS = defaultdict(list)\n", 46 | "\n", 47 | "for bm in BMS:\n", 48 | " for f, _ in FIXTURES:\n", 49 | " if f in bm['name']: \n", 50 | " bm_type = get_bm_type(bm['name'])\n", 51 | " kv_size = get_kv_size(bm['name'])\n", 52 | " if kv_size == (16, 100): continue\n", 53 | " RUNS[(f, bm_type)].append((kv_size, bm))\n", 54 | " \n", 55 | " for _, run in RUNS.items():\n", 56 | " run.sort(key=lambda x: x[0]) \n", 57 | " \n", 58 | "# pprint(RUNS)" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": null, 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "fig, (insert_ax, get_ax) = plt.subplots(1, 2, figsize=(10, 3))\n", 68 | "\n", 69 | "\n", 70 | "def get_bm_ax(bm_type):\n", 71 | " if bm_type == 'insert': return insert_ax\n", 72 | " if bm_type == 'get': return get_ax\n", 73 | " raise RuntimeError(f\"Unknown bm_type {bm_type}\")\n", 74 | "\n", 75 | "sizes = [16, 116, 216, 1000]\n", 76 | "sizes = [16, 216, 532, 1000]\n", 77 | "# sizes = [1, 2, 3, 4]\n", 78 | "\n", 79 | "for bm_type, letter in zip(BM_TYPES, ['a', 'b']):\n", 80 | " ax = get_bm_ax(bm_type)\n", 81 | " ax.set_title(f\"({letter}) {bm_type.upper()}\", fontsize=20)\n", 82 | " for (fixture, _) in FIXTURES:\n", 83 | " r = [run[1]['items_per_second'] / MILLION for run in RUNS[(fixture, bm_type)]]\n", 84 | " style = STYLES[fixture]\n", 85 | " x_vals = sizes[1:] if fixture == PMEMKV[0] or fixture == CRL_STORE[0] else sizes\n", 86 | " ax.plot(x_vals, r, marker=style.marker, ms=8, #ms=style.marker_size, \n", 87 | " color=style.color, markeredgewidth=0.5, lw=2)\n", 88 | " ax.set_xticks(sizes)\n", 89 | " ax.set_xticklabels([str(x) for x in sizes])\n", 90 | " ax.set_xticklabels(['8/8', '16/200', '32/500', '100/900'])\n", 91 | " ax.grid(axis='y', which='major')\n", 92 | " for tick in ax.xaxis.get_major_ticks():\n", 93 | " tick.label.set_fontsize(19)\n", 94 | " for tick in ax.yaxis.get_major_ticks():\n", 95 | " tick.label.set_fontsize(19)\n", 96 | "\n", 97 | "\n", 98 | "insert_ax.set_ylabel(\"Throughput (Mops/s)\", fontsize=18)\n", 99 | "insert_ax.set_title(f\"(a) PUT\", fontsize=20)\n", 100 | "insert_ax.set_ylim(0, 22)\n", 101 | "get_ax.set_ylim(0, 45)\n", 102 | "\n", 103 | "fig.text(0.542, -0.05, \"Key/Value Record Size in Byte\", ha='center', fontsize=20)\n", 104 | "\n", 105 | "fig.legend(loc='upper center', labels=[f[1] for f in FIXTURES], \n", 106 | " bbox_to_anchor=(0.5, 1.13), ncol=6, frameon=False, fontsize=18,\n", 107 | " columnspacing=1, handletextpad=0.3)\n", 108 | "\n", 109 | "plt.tight_layout()\n", 110 | "for ax in (insert_ax, get_ax):\n", 111 | " hide_border(ax, True)\n", 112 | " \n", 113 | "fig.savefig('charts/kv_size.pdf', bbox_inches='tight')\n", 114 | "fig.savefig('charts/kv_size.svg', bbox_inches='tight')" 115 | ] 116 | } 117 | ], 118 | "metadata": { 119 | "kernelspec": { 120 | "display_name": "Python 3", 121 | "language": "python", 122 | "name": "python3" 123 | }, 124 | "language_info": { 125 | "codemirror_mode": { 126 | "name": "ipython", 127 | "version": 3 128 | }, 129 | "file_extension": ".py", 130 | "mimetype": "text/x-python", 131 | "name": "python", 132 | "nbconvert_exporter": "python", 133 | "pygments_lexer": "ipython3", 134 | "version": "3.9.1" 135 | } 136 | }, 137 | "nbformat": 4, 138 | "nbformat_minor": 4 139 | } 140 | -------------------------------------------------------------------------------- /benchmark/benchmark.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace viper::kv_bm { 9 | 10 | static constexpr double SCALE_FACTOR = 1; 11 | static constexpr uint64_t NUM_BASE_PREFILLS = 100'000'000; 12 | static constexpr uint64_t NUM_BASE_OPS = 50'000'000; 13 | static constexpr uint64_t NUM_OPS = NUM_BASE_OPS * SCALE_FACTOR; 14 | 15 | static constexpr uint64_t NUM_PREFILLS = NUM_BASE_PREFILLS * SCALE_FACTOR; 16 | static constexpr uint64_t NUM_INSERTS = NUM_OPS; 17 | static constexpr uint64_t NUM_UPDATES = NUM_OPS; 18 | static constexpr uint64_t NUM_FINDS = NUM_OPS; 19 | static constexpr uint64_t NUM_DELETES = NUM_OPS; 20 | static constexpr uint64_t MAX_DATA_SIZE = NUM_PREFILLS + NUM_INSERTS; 21 | static constexpr uint64_t NUM_REPETITIONS = 1; 22 | static constexpr uint64_t NUM_MAX_THREADS = 36; 23 | static constexpr uint64_t NUM_UTIL_THREADS = 36; 24 | static constexpr benchmark::TimeUnit BM_TIME_UNIT = benchmark::TimeUnit::kMicrosecond; 25 | 26 | template 27 | struct BMRecord { 28 | static_assert(N > 0, "N needs to be greater than 0"); 29 | static constexpr size_t total_size = N * sizeof(T); 30 | 31 | BMRecord() {} 32 | 33 | static BMRecord max_value() { 34 | return BMRecord(std::numeric_limits::max()); 35 | } 36 | 37 | BMRecord(uint64_t x) { data.fill(static_cast(x)); } 38 | BMRecord(uint32_t x) { data.fill(static_cast(x)); } 39 | 40 | inline bool operator==(const BMRecord& other) const { 41 | return data == other.data; 42 | } 43 | 44 | inline bool operator!=(const BMRecord& other) const { 45 | return data != other.data; 46 | } 47 | 48 | bool operator<(const BMRecord &rhs) const { 49 | return get_key() < rhs.get_key(); 50 | } 51 | 52 | bool operator>(const BMRecord &rhs) const { 53 | return rhs < *this; 54 | } 55 | 56 | bool operator<=(const BMRecord &rhs) const { 57 | return !(rhs < *this); 58 | } 59 | 60 | bool operator>=(const BMRecord &rhs) const { 61 | return !(*this < rhs); 62 | } 63 | 64 | BMRecord& from_str(const std::string& bytes) { 65 | assert(bytes.size() == total_size); 66 | const char* raw = static_cast(bytes.data()); 67 | char* raw_data = static_cast(static_cast(data.data())); 68 | std::copy(raw, raw + total_size, raw_data); 69 | return *this; 70 | } 71 | 72 | uint64_t get_key() const { 73 | return *(uint64_t*) data.data(); 74 | } 75 | 76 | void update_value() { 77 | data[0]++; 78 | } 79 | 80 | std::array data; 81 | }; 82 | 83 | 84 | using Offset = uint64_t; 85 | 86 | using KeyType8 = BMRecord; 87 | using KeyType16 = BMRecord; 88 | using KeyType32 = BMRecord; 89 | using KeyType100 = BMRecord; 90 | using ValueType8 = KeyType8; 91 | using ValueType100 = KeyType100; 92 | using ValueType200 = BMRecord; 93 | using ValueType500 = BMRecord; 94 | using ValueType900 = BMRecord; 95 | 96 | #if defined(NVRAM01) 97 | static constexpr size_t CPU_AFFINITY_OFFSET = 36; 98 | static constexpr char VIPER_POOL_FILE[] = "/dev/dax1.1"; 99 | static constexpr char DB_PMEM_DIR[] = "/mnt/pmem2/viper"; 100 | static constexpr char DB_FILE_DIR[] = "/scratch/lawrence.benson/viper-dev/data/"; 101 | static constexpr char RESULT_FILE_DIR[] = "/scratch/lawrence.benson/viper-dev/results/"; 102 | static constexpr char CONFIG_DIR[] = "/scratch/lawrence.benson/viper-dev/benchmark/config/"; 103 | #elif defined(NVRAM02) 104 | static constexpr char VIPER_POOL_FILE[] = "/dev/dax0.0"; 105 | static constexpr char DB_PMEM_DIR[] = "/mnt/nvram-viper"; 106 | static constexpr char DB_FILE_DIR[] = "/scratch/viper"; 107 | static constexpr char RESULT_FILE_DIR[] = "/hpi/fs00/home/lawrence.benson/clion/viper/results/"; 108 | static constexpr char CONFIG_DIR[] = "/hpi/fs00/home/lawrence.benson/clion/viper/benchmark/config/"; 109 | static constexpr size_t CPU_AFFINITY_OFFSET = 0; 110 | #else 111 | static constexpr char VIPER_POOL_FILE[] = "/dev/dax0.0"; 112 | static constexpr char DB_PMEM_DIR[] = "/mnt/pmem/"; 113 | static constexpr char DB_FILE_DIR[] = "/home/user/data/"; 114 | static constexpr char RESULT_FILE_DIR[] = "/home/user/viper/results/"; 115 | static constexpr char CONFIG_DIR[] = "/home/user/viper/benchmark/config/"; 116 | static constexpr size_t CPU_AFFINITY_OFFSET = 0; // 0 or #logical-cpu-per-socket 117 | //static_assert(false, "Need to set these variables for unknown host."); 118 | #endif 119 | 120 | static constexpr uint64_t ONE_GB = (1024ul*1024*1024) * 1; // 1GB 121 | static constexpr uint64_t BM_POOL_SIZE = ONE_GB; 122 | 123 | std::string get_time_string(); 124 | std::string get_output_file(const std::string& bm_name); 125 | int bm_main(std::vector args); 126 | 127 | } // namespace viper::kv_bm 128 | 129 | template 130 | std::ostream& operator<<(std::ostream& s, const viper::kv_bm::BMRecord& rec) { 131 | s << "BMRecord[s" << N << ", data=" << rec.data[0] << "]"; 132 | return s; 133 | } 134 | -------------------------------------------------------------------------------- /eval/var_size.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%load_ext autoreload\n", 10 | "%autoreload 2\n", 11 | "from common import *" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "RESULT_JSON = \"/Users/law/repos/viper/results/var_size/var_size_revision.json\"" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": { 27 | "scrolled": true 28 | }, 29 | "outputs": [], 30 | "source": [ 31 | "from collections import defaultdict\n", 32 | "import json\n", 33 | "\n", 34 | "runs = defaultdict(list)\n", 35 | "\n", 36 | "results_raw = json.loads(open(RESULT_JSON).read())\n", 37 | "# assert results_raw[\"context\"][\"library_build_type\"] != \"debug\"\n", 38 | "BENCHMARKS = results_raw[\"benchmarks\"]\n", 39 | "# print(BENCHMARKS)\n", 40 | "\n", 41 | "\n", 42 | "ALL_FIXTURES = [VIPER, DASH, PMEMKV, HYBRID_FASTER, CRL_STORE]\n", 43 | "ALL_BM_TYPES = ['insert', 'get']\n", 44 | "\n", 45 | "def get_bm_type(bm_type_str):\n", 46 | " for t in ALL_BM_TYPES:\n", 47 | " if t in bm_type_str:\n", 48 | " return t\n", 49 | " raise RuntimeError(f\"Unknown bm_type: {bm_type_str}\")\n", 50 | "\n", 51 | "for bm in BENCHMARKS:\n", 52 | " found = False\n", 53 | " for (fixture, _) in ALL_FIXTURES:\n", 54 | " bm_type = get_bm_type(bm['name'])\n", 55 | " if fixture in bm['name']:\n", 56 | " runs[(fixture, bm_type)].append(bm)\n", 57 | " found = True\n", 58 | " break\n", 59 | "# if not found: raise RuntimeError(f\"Unknown fixture {bm['name']}\")\n", 60 | "\n", 61 | "for fixture, _ in ALL_FIXTURES:\n", 62 | " for bm_type in ALL_BM_TYPES:\n", 63 | " runs[(fixture, bm_type)].sort(key=lambda x: x['threads'])\n", 64 | "\n", 65 | "# pprint(runs)" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": null, 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "fig, (insert_ax, get_ax) = plt.subplots(1, 2, figsize=(10, 3))\n", 75 | "\n", 76 | "\n", 77 | "def get_bm_ax(bm_type):\n", 78 | " if bm_type == 'insert': return insert_ax\n", 79 | " if bm_type == 'get': return get_ax\n", 80 | " if bm_type == 'update': return update_ax\n", 81 | " if bm_type == 'delete': return delete_ax\n", 82 | " raise RuntimeError(f\"Unknown bm_type {bm_type}\")\n", 83 | "\n", 84 | "num_threads = sorted({run['threads'] for run in list(runs.values())[0]})\n", 85 | "\n", 86 | "for bm_type, letter in zip(ALL_BM_TYPES, ['a', 'b']):\n", 87 | " ax = get_bm_ax(bm_type)\n", 88 | " ax.set_xlabel(\"# Threads\", fontsize=20)\n", 89 | " ax.set_title(f\"({letter}) {bm_type.upper()}\", fontsize=18)\n", 90 | " for (fixture, _) in ALL_FIXTURES:\n", 91 | " r = [run['items_per_second'] / MILLION for run in runs[(fixture, bm_type)]]\n", 92 | " style = STYLES[fixture]\n", 93 | " x_vals = [1,2,4,8,16,18] if bm_type == 'insert' and fixture == CRL_STORE[0] else num_threads\n", 94 | " ax.plot(x_vals, r, marker=style.marker, ms=8, color=style.color, lw=3)\n", 95 | " x_ticks = np.append(ax.get_xticks(), 36)\n", 96 | " ax.set_xticks([1, 4, 8, 16, 24, 32, 36])\n", 97 | " ax.set_xlim(0, 37)\n", 98 | " ax.grid(axis='y', which='major')\n", 99 | " for tick in ax.xaxis.get_major_ticks():\n", 100 | " tick.label.set_fontsize(18)\n", 101 | " for tick in ax.yaxis.get_major_ticks():\n", 102 | " tick.label.set_fontsize(18)\n", 103 | "\n", 104 | "\n", 105 | "insert_ax.set_ylabel(\"Throughput\\n(Mops/s)\", fontsize=20)\n", 106 | "insert_ax.set_title(f\"(a) PUT\", fontsize=20)\n", 107 | "insert_ax.set_ylim(0, 15)\n", 108 | "insert_ax.set_yticks(range(0, 16, 3))\n", 109 | "\n", 110 | "get_ax.set_ylim(0, 20)\n", 111 | "\n", 112 | "fig.legend(loc='upper center', labels=[f[1] for f in ALL_FIXTURES], \n", 113 | " bbox_to_anchor=(0.5, 1.15), ncol=6, frameon=False, fontsize=18,\n", 114 | " columnspacing=1.3, handletextpad=0.3)\n", 115 | "\n", 116 | "plt.tight_layout()\n", 117 | "for ax in (insert_ax, get_ax):\n", 118 | " hide_border(ax, True)\n", 119 | "fig.savefig('charts/var_size.pdf', bbox_inches='tight')\n", 120 | "fig.savefig('charts/var_size.svg', bbox_inches='tight')" 121 | ] 122 | } 123 | ], 124 | "metadata": { 125 | "kernelspec": { 126 | "display_name": "Python 3", 127 | "language": "python", 128 | "name": "python3" 129 | }, 130 | "language_info": { 131 | "codemirror_mode": { 132 | "name": "ipython", 133 | "version": 3 134 | }, 135 | "file_extension": ".py", 136 | "mimetype": "text/x-python", 137 | "name": "python", 138 | "nbconvert_exporter": "python", 139 | "pygments_lexer": "ipython3", 140 | "version": "3.9.1" 141 | } 142 | }, 143 | "nbformat": 4, 144 | "nbformat_minor": 4 145 | } 146 | -------------------------------------------------------------------------------- /eval/viper_versions.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%load_ext autoreload\n", 10 | "%autoreload 2\n", 11 | "from common import *" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "RESULT_JSON = \"/Users/law/repos/viper/results/all_ops/all_ops_viper_versions.json\"" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": { 27 | "scrolled": true 28 | }, 29 | "outputs": [], 30 | "source": [ 31 | "FIXTURES = [VIPER, VIPER_DRAM, VIPER_PMEM, VIPER_UNALIGNED]\n", 32 | "BM_TYPES = ['insert', 'get']\n", 33 | "\n", 34 | "BMS = get_all_runs(RESULT_JSON)\n", 35 | "\n", 36 | "RUNS = defaultdict(list)\n", 37 | "\n", 38 | "for bm in BMS:\n", 39 | " if 'median' not in bm['name']: continue\n", 40 | " for (fixture, _) in FIXTURES:\n", 41 | " bm_type = get_bm_type(bm['name'])\n", 42 | " if fixture in bm['name']:\n", 43 | " RUNS[bm_type].append((fixture, bm))\n", 44 | " break\n", 45 | " \n", 46 | "for _, run in RUNS.items():\n", 47 | " run.sort(key=lambda x: x[0], reverse=True) \n", 48 | "\n", 49 | "# pprint(RUNS)" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "metadata": {}, 56 | "outputs": [], 57 | "source": [ 58 | "fig, ax = plt.subplots(1, 1, figsize=(4, 2.5))\n", 59 | "\n", 60 | "# Set position of bar on X axis\n", 61 | "bar_width = 0.20\n", 62 | "bar_diff = 0.02\n", 63 | "r1 = np.arange(3)\n", 64 | "r2 = [x + bar_width + bar_diff for x in r1]\n", 65 | "r3 = [x + bar_width + bar_diff for x in r2]\n", 66 | "r4 = [x + bar_width + bar_diff for x in r3]\n", 67 | "\n", 68 | "Rs = [r1, r2, r3, r4]\n", 69 | "\n", 70 | "plt.rcParams['hatch.linewidth'] = 3\n", 71 | "\n", 72 | "viper_insert = RUNS['insert'][0][1]['items_per_second'] / MILLION\n", 73 | "viper_get = RUNS['get'][0][1]['items_per_second'] / MILLION\n", 74 | "\n", 75 | "for i, (f, bm) in enumerate(RUNS['insert']):\n", 76 | " color = STYLES[f].color\n", 77 | " hatch = '/' if f == VIPER_UNALIGNED[0] else ''\n", 78 | " x_pos = Rs[i][0]\n", 79 | " tp = bm['items_per_second'] / MILLION\n", 80 | " ax.bar(x_pos, tp, bar_width, label=f, color=color, \n", 81 | " hatch=hatch, edgecolor='white')\n", 82 | " diff = int(((tp - viper_insert) / viper_insert) * 100)\n", 83 | " if (diff != 0):\n", 84 | " diff_str = f\"{'+' if diff > 0 else '–'}{abs(diff)}%\"\n", 85 | " ax.text(x_pos, tp + 2, diff_str, rotation=90, fontsize=16, \n", 86 | " va='bottom', ha='center')\n", 87 | "\n", 88 | "for i, (f, bm) in enumerate(RUNS['get']):\n", 89 | " color = STYLES[f].color\n", 90 | " hatch = '/' if f == VIPER_UNALIGNED[0] else ''\n", 91 | " x_pos = Rs[i][1]\n", 92 | " tp = bm['items_per_second'] / MILLION\n", 93 | " ax.bar(x_pos, tp, bar_width, label=f, color=color, \n", 94 | " hatch=hatch, edgecolor='white')\n", 95 | " diff = int(((tp - viper_get) / viper_get) * 100)\n", 96 | " if (f != VIPER[0]):\n", 97 | " diff_str = f\"{'+' if diff > 0 else '–'}{abs(diff)}%\"\n", 98 | " y_pos = tp + 2\n", 99 | " c = 'black'\n", 100 | " if f == VIPER_DRAM[0]: \n", 101 | " y_pos = tp - 21\n", 102 | " c= 'white'\n", 103 | " ax.text(x_pos, y_pos, diff_str, rotation=90, fontsize=16, \n", 104 | " va='bottom', ha='center', color=c)\n", 105 | "\n", 106 | "\n", 107 | "# ax.set_xticks([r + (1.5 * bar_width) for r in range(2)])\n", 108 | "ax.set_xticks([(r2[0] + r3[0]) / 2, (r2[1] + r3[1]) / 2])\n", 109 | "ax.set_axisbelow(True)\n", 110 | "ax.grid(axis='y', which='major')\n", 111 | "for tick in ax.yaxis.get_major_ticks():\n", 112 | " tick.label.set_fontsize(18)\n", 113 | "\n", 114 | "\n", 115 | "ax.set_ylabel(\"Throughput (Mops/s)\", fontsize=18)\n", 116 | "ax.yaxis.set_label_coords(-0.15, 0.55)\n", 117 | "ax.set_xticklabels(['PUT', 'GET'], fontsize=20)\n", 118 | "\n", 119 | "fig.legend(loc='upper center', bbox_to_anchor=(0.55, 1.2), \n", 120 | " labels=['Viper', 'Unaligned', 'PMem', 'DRAM'],\n", 121 | " ncol=2, frameon=False, fontsize=18, \n", 122 | " columnspacing=0.4, handletextpad=0.2, borderpad=0.1, \n", 123 | " labelspacing=0.1, handlelength=1.8)\n", 124 | "\n", 125 | "hide_border(ax)\n", 126 | "plt.tight_layout()\n", 127 | "fig.savefig('charts/viper_versions.pdf', bbox_inches='tight')\n", 128 | "fig.savefig('charts/viper_versions.svg', bbox_inches='tight')" 129 | ] 130 | } 131 | ], 132 | "metadata": { 133 | "kernelspec": { 134 | "display_name": "Python 3", 135 | "language": "python", 136 | "name": "python3" 137 | }, 138 | "language_info": { 139 | "codemirror_mode": { 140 | "name": "ipython", 141 | "version": 3 142 | }, 143 | "file_extension": ".py", 144 | "mimetype": "text/x-python", 145 | "name": "python", 146 | "nbconvert_exporter": "python", 147 | "pygments_lexer": "ipython3", 148 | "version": "3.9.1" 149 | } 150 | }, 151 | "nbformat": 4, 152 | "nbformat_minor": 4 153 | } 154 | -------------------------------------------------------------------------------- /benchmark/reclaim_bm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "benchmark.hpp" 4 | #include "fixtures/viper_fixture.hpp" 5 | 6 | using namespace viper::kv_bm; 7 | 8 | constexpr size_t RECLAIM_NUM_OPS_PER_ITERATION = 50'000; 9 | constexpr size_t RECLAIM_NUM_OP_THREADS = 32; 10 | constexpr size_t RECLAIM_NUM_WRITE_IT = 200; 11 | constexpr size_t RECLAIM_NUM_READ_IT = 4000; 12 | constexpr size_t RECLAIM_NUM_MIXED_IT = 400; 13 | 14 | std::atomic reclaim_done = false; 15 | 16 | #define GENERAL_ARGS \ 17 | ->Iterations(1) \ 18 | ->Unit(BM_TIME_UNIT) \ 19 | ->UseRealTime() 20 | 21 | #define DEFINE_VAR_BM(method) \ 22 | BENCHMARK_TEMPLATE2_DEFINE_F(ViperFixture, reclaim_var ##_ ##method, std::string, std::string)(benchmark::State& state) { \ 23 | bm_reclaim(state, *this, #method); } \ 24 | BENCHMARK_REGISTER_F(ViperFixture, reclaim_var ##_ ##method) GENERAL_ARGS \ 25 | ->Threads(RECLAIM_NUM_OP_THREADS + 1)->Threads(RECLAIM_NUM_OP_THREADS); 26 | // Reclaim with extra thread No reclaim 27 | 28 | #define DEFINE_FIXED_BM(method) \ 29 | BENCHMARK_TEMPLATE2_DEFINE_F(ViperFixture, reclaim_fixed ##_ ##method, KeyType16, ValueType200)(benchmark::State& state) { \ 30 | bm_reclaim(state, *this, #method); } \ 31 | BENCHMARK_REGISTER_F(ViperFixture, reclaim_fixed ##_ ##method) GENERAL_ARGS \ 32 | ->Threads(RECLAIM_NUM_OP_THREADS + 1)->Threads(RECLAIM_NUM_OP_THREADS); 33 | // Reclaim with extra thread No reclaim 34 | 35 | template 36 | inline void bm_reclaim(benchmark::State& state, VFixture& fixture, std::string method) { 37 | viper::ViperConfig v_config{}; 38 | v_config.enable_reclamation = false; 39 | v_config.reclaim_free_percentage = 0; 40 | const bool should_reclaim = state.threads > RECLAIM_NUM_OP_THREADS; 41 | const bool is_reclaim_thread = state.thread_index == RECLAIM_NUM_OP_THREADS; 42 | 43 | set_cpu_affinity(state.thread_index); 44 | 45 | const size_t num_prefills = method == "READ" ? 100'000'000 : 10'000'000; 46 | const size_t num_deletes = num_prefills / 3; 47 | const size_t num_total_values = num_prefills * 2; 48 | 49 | if (is_init_thread(state)) { 50 | if constexpr (std::is_same_v) { 51 | fixture.generate_strings(num_total_values, 16, 200); 52 | } 53 | 54 | fixture.InitMap(num_prefills, v_config); 55 | fixture.setup_and_delete(0, num_prefills - 1, num_deletes); 56 | } 57 | 58 | const size_t ops_per_thread = num_prefills / RECLAIM_NUM_OP_THREADS; 59 | size_t ops_performed = 0; 60 | 61 | uint64_t duration_ms = 0; 62 | for (auto _ : state) { 63 | auto start = std::chrono::high_resolution_clock::now(); 64 | 65 | if (should_reclaim && is_reclaim_thread) { 66 | // For this benchmark, the `reclaim` method needs to be made public in Viper. 67 | fixture.getViper()->reclaim(); 68 | reclaim_done.store(true); 69 | } else { 70 | size_t base_i = (state.thread_index * ops_per_thread) + num_prefills; 71 | const bool is_mixed = method == "MIXED"; 72 | bool is_writing = method == "WRITE"; 73 | bool is_reading = method == "READ"; 74 | const size_t max_iterations = is_writing ? 75 | RECLAIM_NUM_WRITE_IT : 76 | (is_reading ? RECLAIM_NUM_READ_IT : RECLAIM_NUM_MIXED_IT); 77 | size_t iterations = 0; 78 | assert(is_writing || is_mixed || is_reading); 79 | 80 | size_t start_i = base_i; 81 | while (!reclaim_done.load()) { 82 | if (start_i + RECLAIM_NUM_OPS_PER_ITERATION > ops_per_thread) { 83 | start_i = base_i; 84 | } 85 | size_t end_i = start_i + RECLAIM_NUM_OPS_PER_ITERATION; 86 | 87 | if (is_writing) { 88 | fixture.insert(start_i, end_i); 89 | start_i += RECLAIM_NUM_OPS_PER_ITERATION; 90 | } else { 91 | fixture.setup_and_find(0, num_prefills - 1, RECLAIM_NUM_OPS_PER_ITERATION); 92 | } 93 | 94 | if (is_mixed) { 95 | is_writing = !is_writing; 96 | } 97 | 98 | ops_performed += RECLAIM_NUM_OPS_PER_ITERATION; 99 | if (!should_reclaim && ++iterations == max_iterations) { 100 | reclaim_done.store(true); 101 | } 102 | } 103 | } 104 | 105 | auto end = std::chrono::high_resolution_clock::now(); 106 | auto duration = std::chrono::duration_cast(end - start); 107 | duration_ms = duration.count(); 108 | } 109 | 110 | if (is_reclaim_thread) { 111 | state.counters["reclaim_time_ms"] = duration_ms; 112 | } else { 113 | state.counters["op_time_ms"] = duration_ms; 114 | state.SetItemsProcessed(ops_performed); 115 | } 116 | 117 | if (is_init_thread(state)) { 118 | fixture.DeInitMap(); 119 | reclaim_done.store(false); 120 | } 121 | } 122 | 123 | //DEFINE_FIXED_BM(WRITE); 124 | DEFINE_FIXED_BM(READ); 125 | //DEFINE_FIXED_BM(MIXED); 126 | 127 | //DEFINE_VAR_BM(READ); 128 | //DEFINE_VAR_BM(WRITE); 129 | //DEFINE_VAR_BM(MIXED); 130 | 131 | int main(int argc, char** argv) { 132 | std::string exec_name = argv[0]; 133 | const std::string arg = get_output_file("reclaim/reclaim"); 134 | return bm_main({exec_name, arg}); 135 | // return bm_main({exec_name}); 136 | } 137 | -------------------------------------------------------------------------------- /benchmark/config/rocksdb.conf: -------------------------------------------------------------------------------- 1 | # This is a RocksDB option file. 2 | # 3 | # For detailed file format spec, please refer to the example file 4 | # in examples/rocksdb_option_file_example.ini 5 | # 6 | 7 | [Version] 8 | rocksdb_version=6.2.2 9 | options_file_version=1.1 10 | 11 | [DBOptions] 12 | avoid_unnecessary_blocking_io=false 13 | two_write_queues=false 14 | allow_ingest_behind=false 15 | writable_file_max_buffer_size=1048576 16 | avoid_flush_during_shutdown=false 17 | avoid_flush_during_recovery=false 18 | info_log_level=INFO_LEVEL 19 | access_hint_on_compaction_start=NORMAL 20 | allow_concurrent_memtable_write=false 21 | enable_pipelined_write=false 22 | stats_dump_period_sec=600 23 | stats_persist_period_sec=600 24 | strict_bytes_per_sync=false 25 | WAL_ttl_seconds=0 26 | WAL_size_limit_MB=0 27 | max_subcompactions=1 28 | dump_malloc_stats=false 29 | db_log_dir= 30 | wal_recovery_mode=kPointInTimeRecovery 31 | log_file_time_to_roll=0 32 | enable_write_thread_adaptive_yield=false 33 | recycle_log_file_num=0 34 | table_cache_numshardbits=1 35 | atomic_flush=false 36 | preserve_deletes=false 37 | stats_history_buffer_size=1048576 38 | max_open_files=500 39 | max_file_opening_threads=16 40 | delete_obsolete_files_period_micros=21600000000 41 | max_background_flushes=2 42 | write_thread_slow_yield_usec=3 43 | base_background_compactions=-1 44 | manual_wal_flush=false 45 | max_background_compactions=4 46 | bytes_per_sync=1048576 47 | max_background_jobs=2 48 | use_fsync=false 49 | fail_if_options_file_error=false 50 | random_access_max_buffer_size=1048576 51 | compaction_readahead_size=2097152 52 | wal_bytes_per_sync=0 53 | new_table_reader_for_compaction_inputs=true 54 | skip_stats_update_on_db_open=false 55 | skip_log_error_on_recovery=false 56 | is_fd_close_on_exec=true 57 | use_adaptive_mutex=true 58 | error_if_exists=false 59 | write_thread_max_yield_usec=100 60 | enable_thread_tracking=false 61 | db_write_buffer_size=0 62 | create_missing_column_families=false 63 | paranoid_checks=true 64 | create_if_missing=true 65 | max_manifest_file_size=1073741824 66 | allow_2pc=false 67 | max_total_wal_size=0 68 | use_direct_io_for_flush_and_compaction=true 69 | manifest_preallocation_size=4194304 70 | use_direct_reads=true 71 | delayed_write_rate=16777216 72 | allow_fallocate=true 73 | keep_log_file_num=1000 74 | allow_mmap_reads=false 75 | max_log_file_size=0 76 | allow_mmap_writes=false 77 | advise_random_on_open=true 78 | 79 | [CFOptions "default"] 80 | # Write buffer sizes in memory 81 | write_buffer_size=1073741824 82 | max_write_buffer_number=2 83 | 84 | sample_for_compression=0 85 | compaction_pri=kMinOverlappingRatio 86 | merge_operator=nullptr 87 | compaction_filter_factory=nullptr 88 | memtable_factory=SkipListFactory 89 | memtable_insert_with_hint_prefix_extractor=nullptr 90 | comparator=leveldb.BytewiseComparator 91 | target_file_size_base=67108864 92 | max_sequential_skip_in_iterations=8 93 | compaction_style=kCompactionStyleLevel 94 | max_bytes_for_level_base=268435456 95 | bloom_locality=0 96 | compression_per_level= 97 | memtable_huge_page_size=0 98 | max_successive_merges=0 99 | arena_block_size=8388608 100 | memtable_whole_key_filtering=false 101 | target_file_size_multiplier=1 102 | max_bytes_for_level_multiplier_additional=1:1:1:1:1:1:1 103 | snap_refresh_nanos=500000000 104 | num_levels=7 105 | min_write_buffer_number_to_merge=1 106 | max_write_buffer_number_to_maintain=0 107 | compression=kNoCompression 108 | level0_stop_writes_trigger=36 109 | level0_slowdown_writes_trigger=20 110 | compaction_filter=nullptr 111 | level0_file_num_compaction_trigger=4 112 | max_compaction_bytes=1677721600 113 | compaction_options_universal={allow_trivial_move=false;stop_style=kCompactionStopStyleTotalSize;compression_size_percent=-1;max_size_amplification_percent=200;max_merge_width=4294967295;min_merge_width=2;size_ratio=1;} 114 | memtable_prefix_bloom_size_ratio=0.000000 115 | hard_pending_compaction_bytes_limit=274877906944 116 | ttl=0 117 | table_factory=BlockBasedTable 118 | soft_pending_compaction_bytes_limit=68719476736 119 | prefix_extractor=nullptr 120 | bottommost_compression=kDisableCompressionOption 121 | force_consistency_checks=false 122 | paranoid_file_checks=false 123 | compaction_options_fifo={allow_compaction=false;max_table_files_size=1073741824;} 124 | max_bytes_for_level_multiplier=10.000000 125 | optimize_filters_for_hits=false 126 | level_compaction_dynamic_level_bytes=true 127 | inplace_update_num_locks=10000 128 | inplace_update_support=false 129 | periodic_compaction_seconds=0 130 | disable_auto_compactions=false 131 | report_bg_io_stats=false 132 | 133 | [TableOptions/BlockBasedTable "default"] 134 | pin_top_level_index_and_filter=true 135 | enable_index_compression=true 136 | read_amp_bytes_per_bit=8589934592 137 | format_version=2 138 | block_align=false 139 | metadata_block_size=4096 140 | block_size_deviation=10 141 | partition_filters=false 142 | block_size=4096 143 | index_block_restart_interval=1 144 | no_block_cache=false 145 | checksum=kCRC32c 146 | whole_key_filtering=true 147 | index_shortening=kShortenSeparators 148 | data_block_index_type=kDataBlockBinarySearch 149 | index_type=kBinarySearch 150 | verify_compression=false 151 | filter_policy=nullptr 152 | data_block_hash_table_util_ratio=0.750000 153 | pin_l0_filter_and_index_blocks_in_cache=true 154 | block_restart_interval=16 155 | cache_index_and_filter_blocks_with_high_priority=false 156 | cache_index_and_filter_blocks=true 157 | hash_index_allow_collision=true 158 | flush_block_policy_factory=FlushBlockBySizePolicyFactory 159 | -------------------------------------------------------------------------------- /eval/all_ops.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%load_ext autoreload\n", 10 | "%autoreload 2\n", 11 | "from common import *" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "import json\n", 21 | "\n", 22 | "RESULT_JSON = \"/Users/law/repos/viper/results/all_ops/all_ops_full_revision.json\"\n", 23 | "\n", 24 | "results_raw = json.loads(open(RESULT_JSON).read())\n", 25 | "# assert results_raw[\"context\"][\"library_build_type\"] != \"debug\"\n", 26 | "BENCHMARKS = results_raw[\"benchmarks\"]\n", 27 | "# print(BENCHMARKS)" 28 | ] 29 | }, 30 | { 31 | "cell_type": "code", 32 | "execution_count": null, 33 | "metadata": { 34 | "scrolled": true 35 | }, 36 | "outputs": [], 37 | "source": [ 38 | "from collections import defaultdict\n", 39 | "runs = defaultdict(list)\n", 40 | "\n", 41 | "\n", 42 | "BM_FIXTURES = [VIPER, DASH, PMEMKV, HYBRID_FASTER, UTREE, CRL_STORE, ROCKS]\n", 43 | "BM_TYPES = ['insert', 'get', 'update', 'delete']\n", 44 | "\n", 45 | "def get_bm_type(bm_type_str):\n", 46 | " for t in BM_TYPES:\n", 47 | " if t in bm_type_str:\n", 48 | " return t\n", 49 | " raise RuntimeError(f\"Unknown bm_type: {bm_type_str}\")\n", 50 | "\n", 51 | "for bm in BENCHMARKS:\n", 52 | " found = False\n", 53 | " for (fixture, _) in BM_FIXTURES:\n", 54 | " bm_type = get_bm_type(bm['name'])\n", 55 | " if fixture in bm['name']:\n", 56 | " runs[(fixture, bm_type)].append(bm)\n", 57 | " found = True\n", 58 | " break\n", 59 | "# if not found: raise RuntimeError(f\"Unknown fixture {bm['name']}\")\n", 60 | "\n", 61 | "for fixture, _ in BM_FIXTURES:\n", 62 | " for bm_type in BM_TYPES:\n", 63 | " runs[(fixture, bm_type)].sort(key=lambda x: x['threads'])\n", 64 | "\n", 65 | "# pprint(runs)" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": null, 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "fig, (insert_ax, get_ax, update_ax, delete_ax) = plt.subplots(1, 4, figsize=(20, 3))\n", 75 | "\n", 76 | "\n", 77 | "rcParams.update(plt.rcParamsDefault)\n", 78 | "plt.style.use('default')\n", 79 | "\n", 80 | "def get_bm_ax(bm_type):\n", 81 | " if bm_type == 'insert': return insert_ax\n", 82 | " if bm_type == 'get': return get_ax\n", 83 | " if bm_type == 'update': return update_ax\n", 84 | " if bm_type == 'delete': return delete_ax\n", 85 | " raise RuntimeError(f\"Unknown bm_type {bm_type}\")\n", 86 | "\n", 87 | "num_threads = sorted({run['threads'] for run in list(runs.values())[0]})\n", 88 | "\n", 89 | "for bm_type, letter in zip(BM_TYPES, ['a', 'b', 'c', 'd']):\n", 90 | " ax = get_bm_ax(bm_type)\n", 91 | " ax.set_xlabel(\"# Threads\", fontsize=18)\n", 92 | " ax.set_title(f\"({letter}) {bm_type.upper()}\", fontsize=20)\n", 93 | " for (fixture, _) in BM_FIXTURES:\n", 94 | " r = [run['items_per_second'] / MILLION for run in runs[(fixture, bm_type)]]\n", 95 | " style = STYLES[fixture]\n", 96 | " x_vals = [1, 2, 4, 8, 16, 18] if fixture == CRL_STORE[0] and bm_type != 'get' else num_threads\n", 97 | " ax.plot(x_vals, r, marker=style.marker, color=style.color, lw=3, ms=8) \n", 98 | " #, markeredgewidth=0.5, ms=style.marker_size,)\n", 99 | " x_ticks = np.append(ax.get_xticks(), 36)\n", 100 | " ax.set_xticks([1, 4, 8, 16, 24, 32, 36])\n", 101 | " ax.set_xlim(0, 37)\n", 102 | " ax.grid(axis='y', which='major')\n", 103 | " for tick in ax.xaxis.get_major_ticks():\n", 104 | " tick.label.set_fontsize(18)\n", 105 | " for tick in ax.yaxis.get_major_ticks():\n", 106 | " tick.label.set_fontsize(18)\n", 107 | "\n", 108 | "\n", 109 | "insert_ax.set_ylabel(\"Throughput\\n(Mops/s)\", fontsize=18)\n", 110 | "insert_ax.set_title(f\"(a) PUT\", fontsize=20)\n", 111 | "insert_ax.set_ylim(0, 15)\n", 112 | "get_ax.set_ylim(0, 35)\n", 113 | "update_ax.set_ylim(0, 15)\n", 114 | "delete_ax.set_ylim(0, 12)\n", 115 | "\n", 116 | "insert_ax.set_yticks(range(0, 16, 3))\n", 117 | "get_ax.set_yticks(range(0, 36, 5))\n", 118 | "update_ax.set_yticks(range(0, 16, 3))\n", 119 | "delete_ax.set_yticks(range(0, 13, 3))\n", 120 | "\n", 121 | "fig.legend(loc='upper center', labels=[f[1] for f in BM_FIXTURES], \n", 122 | " bbox_to_anchor=(0.5, 1.15), ncol=7, frameon=False, fontsize=18,\n", 123 | " columnspacing=1.3, handletextpad=0.3)\n", 124 | "plt.tight_layout()\n", 125 | "\n", 126 | "for ax in (insert_ax, get_ax, update_ax, delete_ax):\n", 127 | " hide_border(ax, True)\n", 128 | "\n", 129 | "fig.savefig('charts/all_ops.pdf', bbox_inches='tight')\n", 130 | "fig.savefig('charts/all_ops.svg', bbox_inches='tight')" 131 | ] 132 | } 133 | ], 134 | "metadata": { 135 | "kernelspec": { 136 | "display_name": "Python 3", 137 | "language": "python", 138 | "name": "python3" 139 | }, 140 | "language_info": { 141 | "codemirror_mode": { 142 | "name": "ipython", 143 | "version": 3 144 | }, 145 | "file_extension": ".py", 146 | "mimetype": "text/x-python", 147 | "name": "python", 148 | "nbconvert_exporter": "python", 149 | "pygments_lexer": "ipython3", 150 | "version": "3.9.1" 151 | } 152 | }, 153 | "nbformat": 4, 154 | "nbformat_minor": 4 155 | } 156 | -------------------------------------------------------------------------------- /benchmark/ycsb_bm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "benchmark.hpp" 9 | #include "fixtures/common_fixture.hpp" 10 | #include "fixtures/viper_fixture.hpp" 11 | #include "fixtures/faster_fixture.hpp" 12 | #include "fixtures/crl_fixture.hpp" 13 | #include "fixtures/dash_fixture.hpp" 14 | //#include "fixtures/rocksdb_fixture.hpp" 15 | #include "fixtures/pmem_kv_fixture.hpp" 16 | #include "fixtures/ycsb_common.hpp" 17 | 18 | #define YCSB_BM 19 | #define UTREE_KEY_T viper::kv_bm::KeyType8 20 | #include "fixtures/utree_fixture.hpp" 21 | 22 | using namespace viper::kv_bm; 23 | 24 | static constexpr char BASE_DIR[] = "/mnt/nvme2/viper"; 25 | static constexpr char PREFILL_FILE[] = "/ycsb_prefill.dat"; 26 | 27 | #define GENERAL_ARGS \ 28 | ->Repetitions(1) \ 29 | ->Iterations(1) \ 30 | ->Unit(BM_TIME_UNIT) \ 31 | ->UseRealTime() \ 32 | ->ThreadRange(1, NUM_MAX_THREADS) \ 33 | ->Threads(24) 34 | // ->ThreadRange(1, 18) \ 35 | 36 | #define DEFINE_BM(fixture, workload, data) \ 37 | BENCHMARK_TEMPLATE2_DEFINE_F(fixture, workload ## _tp, KeyType8, ValueType200)(benchmark::State& state) { \ 38 | ycsb_run(state, *this, &data, \ 39 | std::string{BASE_DIR} + "/ycsb_wl_" #workload ".dat", false); \ 40 | } \ 41 | BENCHMARK_REGISTER_F(fixture, workload ## _tp) GENERAL_ARGS; \ 42 | BENCHMARK_TEMPLATE2_DEFINE_F(fixture, workload ## _lat, KeyType8, ValueType200)(benchmark::State& state) { \ 43 | ycsb_run(state, *this, &data, \ 44 | std::string{BASE_DIR} + "/ycsb_wl_" #workload ".dat", true); \ 45 | } \ 46 | BENCHMARK_REGISTER_F(fixture, workload ## _lat) GENERAL_ARGS 47 | 48 | #define ALL_BMS(fixture) \ 49 | DEFINE_BM(fixture, 5050_uniform, data_uniform_50_50); \ 50 | DEFINE_BM(fixture, 1090_uniform, data_uniform_10_90); \ 51 | DEFINE_BM(fixture, 5050_zipf, data_zipf_50_50); \ 52 | DEFINE_BM(fixture, 1090_zipf, data_zipf_10_90) 53 | 54 | 55 | static std::vector prefill_data; 56 | static std::vector data_uniform_50_50; 57 | static std::vector data_uniform_10_90; 58 | static std::vector data_zipf_50_50; 59 | static std::vector data_zipf_10_90; 60 | 61 | void ycsb_run(benchmark::State& state, BaseFixture& fixture, std::vector* data, 62 | const std::filesystem::path& wl_file, bool log_latency) { 63 | set_cpu_affinity(state.thread_index); 64 | 65 | if (is_init_thread(state)) { 66 | fixture.InitMap(); 67 | fixture.prefill_ycsb(prefill_data); 68 | if (data->empty()) { 69 | std::cout << "Reading workload file: " << wl_file << std::endl; 70 | ycsb::read_workload_file(wl_file, data); 71 | std::cout << "Done reading workload file." << std::endl; 72 | } 73 | hdr_init(1, 1000000000, 4, &fixture.hdr_); 74 | } 75 | 76 | struct hdr_histogram* hdr; 77 | if (log_latency) { 78 | hdr_init(1, 1000000000, 4, &hdr); 79 | } else { 80 | hdr = nullptr; 81 | } 82 | 83 | uint64_t start_idx = 0; 84 | uint64_t end_idx = 0; 85 | uint64_t op_counter = 0; 86 | for (auto _ : state) { 87 | // Need to do this in here as data might not be loaded yet. 88 | const uint64_t num_total_ops = data->size(); 89 | const uint64_t num_ops_per_thread = num_total_ops / state.threads; 90 | start_idx = state.thread_index * num_ops_per_thread; 91 | end_idx = start_idx + num_ops_per_thread; 92 | 93 | // Actual benchmark 94 | op_counter = fixture.run_ycsb(start_idx, end_idx, *data, hdr); 95 | 96 | state.SetItemsProcessed(num_ops_per_thread); 97 | if (log_latency) { 98 | fixture.merge_hdr(hdr); 99 | hdr_close(hdr); 100 | } 101 | } 102 | 103 | if (is_init_thread(state)) { 104 | if (log_latency) { 105 | hdr_histogram* global_hdr = fixture.get_hdr(); 106 | state.counters["hdr_max"] = hdr_max(global_hdr); 107 | state.counters["hdr_avg"] = hdr_mean(global_hdr); 108 | state.counters["hdr_min"] = hdr_min(global_hdr); 109 | state.counters["hdr_std"] = hdr_stddev(global_hdr); 110 | state.counters["hdr_median"] = hdr_value_at_percentile(global_hdr, 50.0); 111 | state.counters["hdr_90"] = hdr_value_at_percentile(global_hdr, 90.0); 112 | state.counters["hdr_95"] = hdr_value_at_percentile(global_hdr, 95.0); 113 | state.counters["hdr_99"] = hdr_value_at_percentile(global_hdr, 99.0); 114 | state.counters["hdr_999"] = hdr_value_at_percentile(global_hdr, 99.9); 115 | state.counters["hdr_9999"] = hdr_value_at_percentile(global_hdr, 99.99); 116 | // hdr_percentiles_print(global_hdr, stdout, 3, 1.0, CLASSIC); 117 | hdr_close(global_hdr); 118 | } 119 | 120 | fixture.DeInitMap(); 121 | } 122 | 123 | if (op_counter == 0) { 124 | BaseFixture::log_find_count(state, op_counter, end_idx - start_idx); 125 | } 126 | } 127 | 128 | ALL_BMS(ViperFixture); 129 | //ALL_BMS(PmemKVFixture); 130 | //ALL_BMS(UTreeFixture); 131 | //ALL_BMS(CrlFixture); 132 | //ALL_BMS(DashFixture); 133 | 134 | //ALL_BMS(RocksDbFixture); 135 | //ALL_BMS(PmemHybridFasterFixture); 136 | 137 | 138 | int main(int argc, char** argv) { 139 | std::cout << "Prefilling data..." << std::endl; 140 | std::filesystem::path prefill_file = BASE_DIR + std::string{PREFILL_FILE}; 141 | ycsb::read_workload_file(prefill_file, &prefill_data); 142 | 143 | std::string exec_name = argv[0]; 144 | const std::string arg = get_output_file("ycsb/ycsb"); 145 | return bm_main({exec_name, arg}); 146 | // return bm_main({exec_name}); 147 | } 148 | -------------------------------------------------------------------------------- /eval/ycsb.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%load_ext autoreload\n", 10 | "%autoreload 2\n", 11 | "from common import *" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "RESULT_JSON = \"/Users/law/repos/viper/results/ycsb/ycsb_all_revision.json\"" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "import json\n", 30 | "\n", 31 | "results_raw = json.loads(open(RESULT_JSON).read())\n", 32 | "assert results_raw[\"context\"][\"library_build_type\"] != \"debug\"\n", 33 | "BENCHMARKS = results_raw[\"benchmarks\"]\n", 34 | "# print(BENCHMARKS)" 35 | ] 36 | }, 37 | { 38 | "cell_type": "code", 39 | "execution_count": null, 40 | "metadata": { 41 | "scrolled": true 42 | }, 43 | "outputs": [], 44 | "source": [ 45 | "from collections import defaultdict\n", 46 | "runs = defaultdict(list)\n", 47 | "\n", 48 | "FIXTURES = [VIPER, DASH, PMEMKV, HYBRID_FASTER, UTREE, CRL_STORE]\n", 49 | "BM_TYPES = ['5050_uniform', '1090_uniform', '5050_zipf', '1090_zipf']\n", 50 | "\n", 51 | "RUNS = get_benchmarks(BENCHMARKS, FIXTURES, BM_TYPES)\n", 52 | "\n", 53 | "# pprint(RUNS)" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": null, 59 | "metadata": { 60 | "scrolled": false 61 | }, 62 | "outputs": [], 63 | "source": [ 64 | "fig, (lat_axes, tp_axes) = plt.subplots(2, 4, figsize=(20, 5))\n", 65 | "uniform_5050_ax, uniform_1090_ax, zipf_5050_ax, zipf_1090_ax = lat_axes\n", 66 | "uniform_5050_ax_tp, uniform_1090_ax_tp, zipf_5050_ax_tp, zipf_1090_ax_tp = tp_axes\n", 67 | "\n", 68 | "def get_bm_ax(bm_type):\n", 69 | " if bm_type == '5050_uniform': return (uniform_5050_ax, uniform_5050_ax_tp)\n", 70 | " if bm_type == '1090_uniform': return (uniform_1090_ax, uniform_1090_ax_tp)\n", 71 | " if bm_type == '5050_zipf': return (zipf_5050_ax, zipf_5050_ax_tp)\n", 72 | " if bm_type == '1090_zipf': return (zipf_1090_ax, zipf_1090_ax_tp)\n", 73 | " raise RuntimeError(f\"Unknown bm_type {bm_type}\")\n", 74 | "\n", 75 | "num_threads = sorted({run['threads'] for run in list(RUNS.values())[0]})\n", 76 | "SEC_IN_US = 1000000\n", 77 | "\n", 78 | "for bm_type in BM_TYPES:\n", 79 | " (lt_ax, tp_ax) = get_bm_ax(bm_type)\n", 80 | "# lt_ax.set_xlabel(\"# Threads\", fontsize=20)\n", 81 | "# tp_ax.set_xlabel(\"# Threads\", fontsize=20)\n", 82 | " \n", 83 | " for (fixture, _) in FIXTURES: \n", 84 | " lat_runs = [r for r in RUNS[(fixture, bm_type)] if f\"{bm_type}_lat\" in r['name']]\n", 85 | " tp_runs = [r for r in RUNS[(fixture, bm_type)] if f\"{bm_type}_tp\" in r['name']]\n", 86 | " \n", 87 | " tp = [run['items_per_second'] / MILLION for run in tp_runs]\n", 88 | " hdr_avg = [run['hdr_avg'] / 1000 for run in lat_runs]\n", 89 | " \n", 90 | " style = STYLES[fixture]\n", 91 | " x_vals = [1, 2, 4, 8, 16, 18] if fixture == CRL_STORE[0] and bm_type != 'get' else num_threads\n", 92 | " tp_ax.plot(x_vals, tp, marker=style.marker, ms=style.marker_size, \n", 93 | " color=style.color, markeredgewidth=0.5, lw=3)\n", 94 | " if fixture == HYBRID_FASTER[0]:\n", 95 | " x_vals = []\n", 96 | " hdr_avg = []\n", 97 | " lt_ax.plot(x_vals, hdr_avg, marker=style.marker, ms=style.marker_size, \n", 98 | " color=style.color, markeredgewidth=0.5, lw=3)\n", 99 | " \n", 100 | " for ax in [lt_ax, tp_ax]:\n", 101 | " ax.set_xticks([1, 4, 8, 16, 24, 32, 36])\n", 102 | " ax.set_xlim(0, 37)\n", 103 | " ax.grid(axis='y', which='major')\n", 104 | " for tick in ax.xaxis.get_major_ticks():\n", 105 | " tick.label.set_fontsize(18)\n", 106 | " for tick in ax.yaxis.get_major_ticks():\n", 107 | " tick.label.set_fontsize(18)\n", 108 | "\n", 109 | "# lt_ax.set_yscale('log')\n", 110 | " \n", 111 | "\n", 112 | "uniform_5050_ax.set_ylabel(\"Latency\\n(µs/op)\", fontsize=20)\n", 113 | "uniform_5050_ax.set_title(f\"(a) UNIFORM R50:W50\", fontsize=20)\n", 114 | "uniform_1090_ax.set_title(f\"(b) UNIFORM R10:W90\", fontsize=20)\n", 115 | "zipf_5050_ax.set_title(f\"(c) ZIPF R50:W50\", fontsize=20)\n", 116 | "zipf_1090_ax.set_title(f\"(d) ZIPF R10:W90\", fontsize=20)\n", 117 | "\n", 118 | "uniform_5050_ax.set_ylim(0, 20)\n", 119 | "uniform_1090_ax.set_ylim(0, 20)\n", 120 | "zipf_5050_ax.set_ylim(0, 20)\n", 121 | "zipf_1090_ax.set_ylim(0, 20)\n", 122 | "\n", 123 | "uniform_5050_ax_tp.set_ylabel(\"Throughput\\n(Mops/s)\", fontsize=20)\n", 124 | "uniform_5050_ax_tp.set_title(f\"(e) UNIFORM R50:W50\", fontsize=20)\n", 125 | "uniform_1090_ax_tp.set_title(f\"(f) UNIFORM R10:W90\", fontsize=20)\n", 126 | "zipf_5050_ax_tp.set_title(f\"(g) ZIPF R50:W50\", fontsize=20)\n", 127 | "zipf_1090_ax_tp.set_title(f\"(h) ZIPF R10:W90\", fontsize=20)\n", 128 | "\n", 129 | "uniform_5050_ax_tp.set_ylim(0, 30)\n", 130 | "uniform_1090_ax_tp.set_ylim(0, 30)\n", 131 | "zipf_5050_ax_tp.set_ylim(0, 30)\n", 132 | "zipf_1090_ax_tp.set_ylim(0, 30)\n", 133 | "\n", 134 | "fig.text(0.5, -0.05, \"# Threads\", ha='center', fontsize=22)\n", 135 | "\n", 136 | "fig.legend(loc='upper center', labels=[f[1] for f in FIXTURES], \n", 137 | " bbox_to_anchor=(0.5, 1.1), ncol=6, frameon=False, fontsize=20,\n", 138 | " columnspacing=1.3, handletextpad=0.3)\n", 139 | "\n", 140 | "for ax in [*tp_axes, *lat_axes]:\n", 141 | " hide_border(ax, show_left=True)\n", 142 | "plt.tight_layout()\n", 143 | "fig.savefig('charts/ycsb.pdf', bbox_inches='tight')\n", 144 | "fig.savefig('charts/ycsb.svg', bbox_inches='tight')" 145 | ] 146 | } 147 | ], 148 | "metadata": { 149 | "kernelspec": { 150 | "display_name": "Python 3", 151 | "language": "python", 152 | "name": "python3" 153 | }, 154 | "language_info": { 155 | "codemirror_mode": { 156 | "name": "ipython", 157 | "version": 3 158 | }, 159 | "file_extension": ".py", 160 | "mimetype": "text/x-python", 161 | "name": "python", 162 | "nbconvert_exporter": "python", 163 | "pygments_lexer": "ipython3", 164 | "version": "3.9.1" 165 | } 166 | }, 167 | "nbformat": 4, 168 | "nbformat_minor": 4 169 | } 170 | -------------------------------------------------------------------------------- /benchmark/all_ops_benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "benchmark.hpp" 7 | #include "fixtures/common_fixture.hpp" 8 | #include "fixtures/viper_fixture.hpp" 9 | #include "fixtures/utree_fixture.hpp" 10 | #include "fixtures/faster_fixture.hpp" 11 | #include "fixtures/dash_fixture.hpp" 12 | #include "fixtures/tbb_fixture.hpp" 13 | #include "fixtures/crl_fixture.hpp" 14 | #include "fixtures/pmem_kv_fixture.hpp" 15 | #include "fixtures/rocksdb_fixture.hpp" 16 | 17 | using namespace viper::kv_bm; 18 | 19 | constexpr size_t ALL_OPS_NUM_REPETITIONS = 1; 20 | constexpr size_t ALL_OPS_NUM_PREFILLS = 100'000'000; 21 | constexpr size_t ALL_OPS_NUM_INSERTS = 50'000'000; 22 | constexpr size_t ALL_OPS_NUM_FINDS = 50'000'000; 23 | constexpr size_t ALL_OPS_NUM_UPDATES = 50'000'000; 24 | constexpr size_t ALL_OPS_NUM_DELETES = 50'000'000; 25 | 26 | #define GENERAL_ARGS \ 27 | Repetitions(ALL_OPS_NUM_REPETITIONS) \ 28 | ->Iterations(1) \ 29 | ->Unit(BM_TIME_UNIT) \ 30 | ->UseRealTime() \ 31 | ->ThreadRange(1, NUM_MAX_THREADS) \ 32 | ->Threads(24) 33 | 34 | 35 | #define DEFINE_BM(fixture, method) \ 36 | BENCHMARK_TEMPLATE2_DEFINE_F(fixture, method, KeyType16, ValueType200)(benchmark::State& state) { \ 37 | bm_##method(state, *this); \ 38 | } \ 39 | BENCHMARK_REGISTER_F(fixture, method)->GENERAL_ARGS 40 | 41 | #define BM_INSERT(fixture) DEFINE_BM(fixture, insert)->Args({ALL_OPS_NUM_PREFILLS, ALL_OPS_NUM_INSERTS}) 42 | #define BM_FIND(fixture) DEFINE_BM(fixture, get) ->Args({ALL_OPS_NUM_PREFILLS, ALL_OPS_NUM_FINDS}) 43 | #define BM_UPDATE(fixture) DEFINE_BM(fixture, update)->Args({ALL_OPS_NUM_PREFILLS, ALL_OPS_NUM_UPDATES}) 44 | #define BM_DELETE(fixture) DEFINE_BM(fixture, delete)->Args({ALL_OPS_NUM_PREFILLS, ALL_OPS_NUM_DELETES}) 45 | 46 | #define ALL_BMS(fixture) \ 47 | BM_INSERT(fixture); \ 48 | BM_FIND(fixture); \ 49 | BM_UPDATE(fixture); \ 50 | BM_DELETE(fixture) 51 | 52 | 53 | void bm_insert(benchmark::State& state, BaseFixture& fixture) { 54 | const uint64_t num_total_prefill = state.range(0); 55 | const uint64_t num_total_inserts = state.range(1); 56 | 57 | set_cpu_affinity(state.thread_index); 58 | 59 | if (is_init_thread(state)) { 60 | fixture.InitMap(num_total_prefill); 61 | } 62 | 63 | const uint64_t num_inserts_per_thread = (num_total_inserts / state.threads) + 1; 64 | const uint64_t start_idx = (state.thread_index * num_inserts_per_thread) + num_total_prefill; 65 | const uint64_t end_idx = std::min(start_idx + num_inserts_per_thread, num_total_prefill + num_total_inserts); 66 | 67 | uint64_t insert_counter = 0; 68 | for (auto _ : state) { 69 | auto start_op = std::chrono::high_resolution_clock::now(); 70 | insert_counter = fixture.setup_and_insert(start_idx, end_idx); 71 | auto end_op = std::chrono::high_resolution_clock::now(); 72 | state.counters["insert-ns"] = (end_op - start_op).count(); 73 | } 74 | 75 | state.SetItemsProcessed(num_inserts_per_thread); 76 | 77 | if (is_init_thread(state)) { 78 | fixture.DeInitMap(); 79 | } 80 | 81 | BaseFixture::log_find_count(state, insert_counter, end_idx - start_idx); 82 | } 83 | 84 | void bm_update(benchmark::State& state, BaseFixture& fixture) { 85 | const uint64_t num_total_prefill = state.range(0); 86 | const uint64_t num_total_updates = state.range(1); 87 | 88 | set_cpu_affinity(state.thread_index); 89 | 90 | if (is_init_thread(state)) { 91 | fixture.InitMap(num_total_prefill); 92 | } 93 | 94 | const uint64_t num_updates_per_thread = num_total_updates / state.threads; 95 | const uint64_t start_idx = 0; 96 | const uint64_t end_idx = num_total_prefill - state.threads; 97 | 98 | uint64_t update_counter = 0; 99 | for (auto _ : state) { 100 | auto start_op = std::chrono::high_resolution_clock::now(); 101 | update_counter = fixture.setup_and_update(start_idx, end_idx, num_updates_per_thread); 102 | auto end_op = std::chrono::high_resolution_clock::now(); 103 | state.counters["update-ns"] = (end_op - start_op).count(); 104 | } 105 | 106 | state.SetItemsProcessed(num_updates_per_thread); 107 | 108 | if (is_init_thread(state)) { 109 | fixture.DeInitMap(); 110 | } 111 | 112 | BaseFixture::log_find_count(state, update_counter, num_updates_per_thread); 113 | } 114 | 115 | void bm_get(benchmark::State& state, BaseFixture& fixture) { 116 | const uint64_t num_total_prefills = state.range(0); 117 | const uint64_t num_total_finds = state.range(1); 118 | 119 | set_cpu_affinity(state.thread_index); 120 | 121 | if (is_init_thread(state)) { 122 | fixture.InitMap(num_total_prefills); 123 | } 124 | 125 | const uint64_t num_finds_per_thread = (num_total_finds / state.threads) + 1; 126 | const uint64_t start_idx = 0; 127 | const uint64_t end_idx = num_total_prefills - state.threads; 128 | 129 | uint64_t found_counter = 0; 130 | for (auto _ : state) { 131 | auto start_op = std::chrono::high_resolution_clock::now(); 132 | found_counter = fixture.setup_and_find(start_idx, end_idx, num_finds_per_thread); 133 | auto end_op = std::chrono::high_resolution_clock::now(); 134 | state.counters["get-ns"] = (end_op - start_op).count(); 135 | } 136 | 137 | state.SetItemsProcessed(num_finds_per_thread); 138 | 139 | if (is_init_thread(state)) { 140 | fixture.DeInitMap(); 141 | } 142 | 143 | BaseFixture::log_find_count(state, found_counter, num_finds_per_thread); 144 | } 145 | 146 | void bm_delete(benchmark::State& state, BaseFixture& fixture) { 147 | const uint64_t num_total_prefills = state.range(0); 148 | const uint64_t num_total_deletes = state.range(1); 149 | 150 | set_cpu_affinity(state.thread_index); 151 | 152 | if (is_init_thread(state)) { 153 | fixture.InitMap(num_total_prefills); 154 | } 155 | 156 | const uint64_t num_deletes_per_thread = (num_total_deletes / state.threads) + 1; 157 | const uint64_t start_idx = 0; 158 | const uint64_t end_idx = num_total_prefills - state.threads; 159 | 160 | uint64_t found_counter = 0; 161 | for (auto _ : state) { 162 | auto start_op = std::chrono::high_resolution_clock::now(); 163 | found_counter = fixture.setup_and_delete(start_idx, end_idx, num_deletes_per_thread); 164 | auto end_op = std::chrono::high_resolution_clock::now(); 165 | state.counters["delete-ns"] = (end_op - start_op).count(); 166 | } 167 | 168 | state.SetItemsProcessed(found_counter); 169 | 170 | if (is_init_thread(state)) { 171 | fixture.DeInitMap(); 172 | } 173 | 174 | BaseFixture::log_find_count(state, found_counter, found_counter); 175 | } 176 | 177 | //ALL_BMS(DashFixture); 178 | ALL_BMS(ViperFixture); 179 | //ALL_BMS(PmemHybridFasterFixture); 180 | //ALL_BMS(PmemKVFixture); 181 | //ALL_BMS(CrlFixture); 182 | //ALL_BMS(UTreeFixture); 183 | //ALL_BMS(PmemRocksDbFixture); 184 | 185 | 186 | int main(int argc, char** argv) { 187 | std::string exec_name = argv[0]; 188 | const std::string arg = get_output_file("all_ops/all_ops"); 189 | return bm_main({exec_name, arg}); 190 | // return bm_main({exec_name}); 191 | } 192 | -------------------------------------------------------------------------------- /benchmark/fixtures/tbb_fixture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common_fixture.hpp" 4 | #include "../benchmark.hpp" 5 | #include 6 | 7 | namespace viper::kv_bm { 8 | 9 | template 10 | struct TbbFixedKeyCompare { 11 | // Use same impl as tbb_hasher 12 | static const size_t hash_multiplier = tbb::detail::select_size_t_constant<2654435769U, 11400714819323198485ULL>::value; 13 | static size_t hash(const KeyT& a) { 14 | return static_cast(a.data[0]) * hash_multiplier; 15 | } 16 | static bool equal(const KeyT& a, const KeyT& b) { return a == b; } 17 | }; 18 | 19 | template 20 | class TbbFixture : public BaseFixture { 21 | using DramMapType = tbb::concurrent_hash_map>; 22 | 23 | public: 24 | void InitMap(const uint64_t num_prefill_inserts = 0, const bool re_init = true) override; 25 | void DeInitMap() override; 26 | 27 | uint64_t insert(uint64_t start_idx, uint64_t end_idx) final; 28 | 29 | uint64_t setup_and_insert(const uint64_t start_idx, const uint64_t end_idx) final; 30 | uint64_t setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) final; 31 | uint64_t setup_and_delete(uint64_t start_idx, uint64_t end_idx, uint64_t num_deletes) final; 32 | uint64_t setup_and_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates) final; 33 | 34 | uint64_t run_ycsb(uint64_t start_idx, uint64_t end_idx, 35 | const std::vector& data, hdr_histogram* hdr) final; 36 | 37 | protected: 38 | std::unique_ptr dram_map_; 39 | bool map_initialized_ = false; 40 | 41 | }; 42 | 43 | template 44 | void TbbFixture::InitMap(uint64_t num_prefill_inserts, const bool re_init) { 45 | if (map_initialized_ && !re_init) { 46 | return; 47 | } 48 | dram_map_ = std::make_unique(2 << 25); 49 | prefill(num_prefill_inserts); 50 | map_initialized_ = true; 51 | } 52 | 53 | template 54 | void TbbFixture::DeInitMap() { 55 | dram_map_->clear(); 56 | dram_map_ = nullptr; 57 | map_initialized_ = false; 58 | } 59 | 60 | template 61 | uint64_t TbbFixture::insert(uint64_t start_idx, uint64_t end_idx) { 62 | uint64_t insert_counter = 0; 63 | for (uint64_t key = start_idx; key < end_idx; ++key) { 64 | typename DramMapType::accessor result; 65 | const KeyT db_key{key}; 66 | const ValueT value{key}; 67 | const bool new_insert = dram_map_->insert(result, {db_key, value}); 68 | if (!new_insert) { 69 | result->second = value; 70 | } 71 | insert_counter += new_insert; 72 | } 73 | return insert_counter; 74 | } 75 | 76 | template 77 | uint64_t TbbFixture::setup_and_insert(uint64_t start_idx, uint64_t end_idx) { 78 | return insert(start_idx, end_idx); 79 | } 80 | 81 | template 82 | uint64_t TbbFixture::setup_and_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates) { 83 | std::random_device rnd{}; 84 | auto rnd_engine = std::default_random_engine(rnd()); 85 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 86 | 87 | uint64_t update_counter = 0; 88 | for (uint64_t i = 0; i < num_updates; ++i) { 89 | typename DramMapType::accessor result; 90 | const uint64_t key = distrib(rnd_engine); 91 | const KeyT db_key{key}; 92 | const bool found = dram_map_->find(result, db_key); 93 | if (found) { 94 | result->second.update_value(); 95 | ++update_counter; 96 | } 97 | } 98 | 99 | return update_counter; 100 | } 101 | 102 | template 103 | uint64_t TbbFixture::setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) { 104 | std::random_device rnd{}; 105 | auto rnd_engine = std::default_random_engine(rnd()); 106 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 107 | 108 | uint64_t found_counter = 0; 109 | for (uint64_t i = 0; i < num_finds; ++i) { 110 | typename DramMapType::const_accessor result; 111 | const uint64_t key = distrib(rnd_engine); 112 | const KeyT db_key{key}; 113 | const bool found = dram_map_->find(result, db_key); 114 | found_counter += found && result->second.data[0] == key; 115 | } 116 | return found_counter; 117 | } 118 | 119 | template 120 | uint64_t TbbFixture::setup_and_delete(uint64_t start_idx, uint64_t end_idx, uint64_t num_deletes) { 121 | std::random_device rnd{}; 122 | auto rnd_engine = std::default_random_engine(rnd()); 123 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 124 | 125 | uint64_t delete_counter = 0; 126 | for (uint64_t i = 0; i < num_deletes; ++i) { 127 | const uint64_t key = distrib(rnd_engine); 128 | const KeyT db_key{key}; 129 | delete_counter += dram_map_->erase(db_key); 130 | } 131 | return delete_counter; 132 | } 133 | 134 | template 135 | uint64_t TbbFixture::run_ycsb(uint64_t, uint64_t, const std::vector&, hdr_histogram*) { 136 | throw std::runtime_error{"YCSB not implemented for non-ycsb key/value types."}; 137 | } 138 | 139 | template <> 140 | uint64_t TbbFixture::run_ycsb( 141 | uint64_t start_idx, uint64_t end_idx, const std::vector& data, hdr_histogram* hdr) { 142 | uint64_t op_count = 0; 143 | for (int op_num = start_idx; op_num < end_idx; ++op_num) { 144 | const ycsb::Record& record = data[op_num]; 145 | 146 | const auto start = std::chrono::high_resolution_clock::now(); 147 | 148 | switch (record.op) { 149 | case ycsb::Record::Op::INSERT: { 150 | typename DramMapType::accessor result; 151 | op_count += dram_map_->insert(result, {record.key, record.value}); 152 | break; 153 | } 154 | case ycsb::Record::Op::GET: { 155 | typename DramMapType::const_accessor result; 156 | const bool found = dram_map_->find(result, record.key); 157 | op_count += found && result->second.data[0] != 0; 158 | break; 159 | } 160 | case ycsb::Record::Op::UPDATE: { 161 | typename DramMapType::accessor result; 162 | op_count += dram_map_->find(result, record.key); 163 | result->second = record.value; 164 | break; 165 | } 166 | default: { 167 | throw std::runtime_error("Unknown operation: " + std::to_string(record.op)); 168 | } 169 | } 170 | 171 | if (hdr == nullptr) { 172 | continue; 173 | } 174 | 175 | const auto end = std::chrono::high_resolution_clock::now(); 176 | const auto duration = std::chrono::duration_cast(end - start); 177 | hdr_record_value(hdr, duration.count()); 178 | } 179 | 180 | return op_count; 181 | } 182 | 183 | } // namespace viper::kv_bm 184 | -------------------------------------------------------------------------------- /eval/breakdown.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%load_ext autoreload\n", 10 | "%autoreload 2\n", 11 | "from common import *" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "RESULT_JSON = \"/Users/law/repos/viper/results/breakdown/breakdown_revision.json\"" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": { 27 | "scrolled": true 28 | }, 29 | "outputs": [], 30 | "source": [ 31 | "from collections import defaultdict\n", 32 | "runs = defaultdict(list)\n", 33 | "\n", 34 | "BMS = get_all_runs(RESULT_JSON)\n", 35 | "\n", 36 | "# pprint(BMS)" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": null, 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "from matplotlib.ticker import (MultipleLocator, AutoMinorLocator)\n", 46 | "\n", 47 | "TIMERS = (\"lock\", \"pmem\", \"map\")\n", 48 | "\n", 49 | "def get_sum_time(bm, op_type):\n", 50 | " total_ns = 0\n", 51 | " for timer in TIMERS:\n", 52 | " total_ns += bm[f\"{op_type}-{timer}\"]\n", 53 | " return total_ns\n", 54 | "\n", 55 | "st_bm = BMS[0]\n", 56 | "at_bm = BMS[1]\n", 57 | "\n", 58 | "st_insert = get_sum_time(BMS[0], \"insert\")\n", 59 | "st_get = get_sum_time(BMS[0], \"get\")\n", 60 | "st_update = get_sum_time(BMS[0], \"update\")\n", 61 | "st_delete = get_sum_time(BMS[0], \"delete\")\n", 62 | "\n", 63 | "at_insert = get_sum_time(BMS[1], \"insert\")\n", 64 | "at_get = get_sum_time(BMS[1], \"get\")\n", 65 | "at_update = get_sum_time(BMS[1], \"update\")\n", 66 | "at_delete = get_sum_time(BMS[1], \"delete\")\n", 67 | "\n", 68 | "bar_width = 0.5\n", 69 | "st_pos = np.arange(4)\n", 70 | "al_pos = [x + bar_width for x in st_pos]\n", 71 | "POS = [st_pos, al_pos]\n", 72 | "\n", 73 | "# multi thread op time in us\n", 74 | "insert_op_time = 2.35\n", 75 | "get_op_time = 1.11\n", 76 | "update_op_time = 2.61\n", 77 | "delete_op_time = 2.83\n", 78 | "\n", 79 | "\n", 80 | "fig, ax = plt.subplots(1, 1, figsize=(4, 1))\n", 81 | "\n", 82 | "STYLES = {\n", 83 | " 'pmem': (PMEM_COLOR, ''),\n", 84 | " 'map' : (DRAM_COLOR, ''),\n", 85 | " 'lock': (\"#990000\", ''),\n", 86 | " 'val' : (\"#009900\", ''),\n", 87 | "}\n", 88 | "\n", 89 | "text_y = 1.05\n", 90 | "\n", 91 | "# ALL_RUNS = [(st_bm, st_insert, st_get, st_update, st_delete)] \n", 92 | "ALL_RUNS = [(at_bm, at_insert, at_get, at_update, at_delete)]\n", 93 | "for i, (bm, insert, get, update, delete) in enumerate(ALL_RUNS):\n", 94 | " # INSERT\n", 95 | " insert_lock = bm[\"insert-lock\"] / insert\n", 96 | " insert_write = bm[\"insert-pmem\"] / insert\n", 97 | " insert_update = bm[\"insert-map\"] / insert\n", 98 | " ax.bar(POS[i][0], insert_lock, bar_width, bottom=0, \n", 99 | " label=\"Lock\", color=STYLES['lock'][0], hatch=STYLES['lock'][1])\n", 100 | " ax.bar(POS[i][0], insert_update, bar_width, bottom=insert_lock, \n", 101 | " label=\"Map\", color=STYLES['map'][0], hatch=STYLES['map'][1])\n", 102 | " ax.bar(POS[i][0], insert_write, bar_width, bottom=insert_lock + insert_update, \n", 103 | " label=\"PMem\", color=STYLES['pmem'][0], hatch=STYLES['pmem'][1])\n", 104 | " ax.text(POS[i][0], text_y, f\"{insert_op_time}µs\", ha='center', fontsize=12)\n", 105 | " \n", 106 | " # GET\n", 107 | " get_map = bm[\"get-map\"] / get\n", 108 | " get_lock = bm[\"get-lock\"] / get\n", 109 | " get_read = bm[\"get-pmem\"] / get\n", 110 | " ax.bar(POS[i][1], get_lock, bar_width, bottom=0, \n", 111 | " color=STYLES['lock'][0], hatch=STYLES['lock'][1])\n", 112 | " ax.bar(POS[i][1], get_map, bar_width, bottom=get_lock, \n", 113 | " color=STYLES['map'][0], hatch=STYLES['map'][1])\n", 114 | " ax.bar(POS[i][1], get_read, bar_width, bottom=get_map + get_lock,\n", 115 | " color=STYLES['pmem'][0], hatch=STYLES['pmem'][1])\n", 116 | " ax.text(POS[i][1], text_y, f\"{get_op_time}µs\", ha='center', fontsize=12)\n", 117 | " \n", 118 | " # UPDATE\n", 119 | " update_map = bm[\"update-map\"] / update\n", 120 | " update_lock = bm[\"update-lock\"] / update\n", 121 | " update_modify = bm[\"update-pmem\"] / update\n", 122 | " ax.bar(POS[i][2], update_lock, bar_width, bottom=0, \n", 123 | " color=STYLES['lock'][0], hatch=STYLES['lock'][1])\n", 124 | " ax.bar(POS[i][2], update_map, bar_width, bottom=update_lock, \n", 125 | " color=STYLES['map'][0], hatch=STYLES['map'][1])\n", 126 | " ax.bar(POS[i][2], update_modify, bar_width, bottom=update_map + update_lock,\n", 127 | " color=STYLES['pmem'][0], hatch=STYLES['pmem'][1])\n", 128 | " ax.text(POS[i][2], text_y, f\"{update_op_time}µs\", ha='center', fontsize=12)\n", 129 | "\n", 130 | " \n", 131 | " # DELETE\n", 132 | " delete_lock = bm[\"delete-lock\"] / delete\n", 133 | " delete_write = bm[\"delete-pmem\"] / delete\n", 134 | " delete_update = bm[\"delete-map\"] / delete\n", 135 | " ax.bar(POS[i][3], delete_lock, bar_width, bottom=0, \n", 136 | " color=STYLES['lock'][0], hatch=STYLES['lock'][1])\n", 137 | " ax.bar(POS[i][3], delete_update, bar_width, bottom=delete_lock, \n", 138 | " color=STYLES['map'][0], hatch=STYLES['map'][1])\n", 139 | " ax.bar(POS[i][3], delete_write, bar_width, bottom=delete_lock + delete_update,\n", 140 | " color=STYLES['pmem'][0], hatch=STYLES['pmem'][1])\n", 141 | " ax.text(POS[i][3], text_y, f\"{delete_op_time}µs\", ha='center', fontsize=12)\n", 142 | " \n", 143 | " \n", 144 | "plt.rcParams['hatch.linewidth'] = 1.5\n", 145 | "plt.rcParams['hatch.color'] = 'white' \n", 146 | "\n", 147 | "\n", 148 | "ax.set_xticks([r + (0.0 * bar_width) for r in st_pos])\n", 149 | "ax.xaxis.set_tick_params(pad=1)\n", 150 | "ax.set_xticklabels([\"PUT\", \"GET\", \"UPDATE\", \"DELETE\"], fontsize=12)\n", 151 | "\n", 152 | "ax.set_yticks([0, 0.2, 0.4, 0.6, 0.8, 1])\n", 153 | "# ax.set_yticklabels([\"\", 0.2, \"\", 0.6, \"\", 1])\n", 154 | "\n", 155 | "ax.set_ylabel(\"Normalized\\nDuration/op\", fontsize=12)\n", 156 | "ax.yaxis.set_label_coords(-0.13, 0.45)\n", 157 | "\n", 158 | "# Put a legend below current axis\n", 159 | "handles, labels = ax.get_legend_handles_labels()\n", 160 | "ax.legend(handles[::-1], labels[::-1], loc='center right', bbox_to_anchor=(1.32, 0.45), \n", 161 | " ncol=1, frameon=False, framealpha=1,\n", 162 | " fontsize=12, columnspacing=0.3, handletextpad=0.2, labelspacing=0.3,\n", 163 | " handlelength=1.8, borderpad=0.1)\n", 164 | "\n", 165 | "for tick in ax.yaxis.get_major_ticks():\n", 166 | " tick.label.set_fontsize(12)\n", 167 | "\n", 168 | "plt.tick_params(axis='x', bottom=False)\n", 169 | "ax.set_axisbelow(True)\n", 170 | "ax.grid(axis='y', which='major')\n", 171 | "hide_border(ax)\n", 172 | "\n", 173 | "fig.savefig('charts/breakdown.pdf', bbox_inches='tight')\n", 174 | "fig.savefig('charts/breakdown.svg', bbox_inches='tight')" 175 | ] 176 | } 177 | ], 178 | "metadata": { 179 | "kernelspec": { 180 | "display_name": "Python 3", 181 | "language": "python", 182 | "name": "python3" 183 | }, 184 | "language_info": { 185 | "codemirror_mode": { 186 | "name": "ipython", 187 | "version": 3 188 | }, 189 | "file_extension": ".py", 190 | "mimetype": "text/x-python", 191 | "name": "python", 192 | "nbconvert_exporter": "python", 193 | "pygments_lexer": "ipython3", 194 | "version": "3.9.1" 195 | } 196 | }, 197 | "nbformat": 4, 198 | "nbformat_minor": 4 199 | } 200 | -------------------------------------------------------------------------------- /eval/breakdown_half.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%load_ext autoreload\n", 10 | "%autoreload 2\n", 11 | "from common import *" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "RESULT_JSON = \"/Users/law/repos/viper/results/breakdown/breakdown_revision.json\"" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": null, 26 | "metadata": { 27 | "scrolled": true 28 | }, 29 | "outputs": [], 30 | "source": [ 31 | "from collections import defaultdict\n", 32 | "runs = defaultdict(list)\n", 33 | "\n", 34 | "BMS = get_all_runs(RESULT_JSON)\n", 35 | "\n", 36 | "# pprint(BMS)" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": null, 42 | "metadata": {}, 43 | "outputs": [], 44 | "source": [ 45 | "from matplotlib.ticker import (MultipleLocator, AutoMinorLocator)\n", 46 | "\n", 47 | "TIMERS = (\"lock\", \"pmem\", \"map\")\n", 48 | "\n", 49 | "def get_sum_time(bm, op_type):\n", 50 | " total_ns = 0\n", 51 | " for timer in TIMERS:\n", 52 | " total_ns += bm[f\"{op_type}-{timer}\"]\n", 53 | " return total_ns\n", 54 | "\n", 55 | "st_bm = BMS[0]\n", 56 | "at_bm = BMS[1]\n", 57 | "\n", 58 | "st_insert = get_sum_time(BMS[0], \"insert\")\n", 59 | "st_get = get_sum_time(BMS[0], \"get\")\n", 60 | "st_update = get_sum_time(BMS[0], \"update\")\n", 61 | "st_delete = get_sum_time(BMS[0], \"delete\")\n", 62 | "\n", 63 | "at_insert = get_sum_time(BMS[1], \"insert\")\n", 64 | "at_get = get_sum_time(BMS[1], \"get\")\n", 65 | "at_update = get_sum_time(BMS[1], \"update\")\n", 66 | "at_delete = get_sum_time(BMS[1], \"delete\")\n", 67 | "\n", 68 | "bar_width = 0.7\n", 69 | "st_pos = np.arange(4)\n", 70 | "al_pos = [x + bar_width for x in st_pos]\n", 71 | "POS = [st_pos, al_pos]\n", 72 | "\n", 73 | "# multi thread op time in us\n", 74 | "insert_op_time = 2.35\n", 75 | "get_op_time = 1.11\n", 76 | "update_op_time = 2.61\n", 77 | "delete_op_time = 2.83\n", 78 | "\n", 79 | "\n", 80 | "fig, ax = plt.subplots(1, 1, figsize=(2.5, 2.5))\n", 81 | "\n", 82 | "STYLES = {\n", 83 | " 'pmem': (PMEM_COLOR, ''),\n", 84 | " 'map' : (DRAM_COLOR, ''),\n", 85 | " 'lock': (\"#990000\", ''),\n", 86 | " 'val' : (\"#009900\", ''),\n", 87 | "}\n", 88 | "\n", 89 | "text_y = 1.05\n", 90 | "dur_fs = 18\n", 91 | "text_rot = 40\n", 92 | "\n", 93 | "# ALL_RUNS = [(st_bm, st_insert, st_get, st_update, st_delete)] \n", 94 | "ALL_RUNS = [(at_bm, at_insert, at_get, at_update, at_delete)]\n", 95 | "for i, (bm, insert, get, update, delete) in enumerate(ALL_RUNS):\n", 96 | " # INSERT\n", 97 | " insert_lock = bm[\"insert-lock\"] / insert\n", 98 | " insert_write = bm[\"insert-pmem\"] / insert\n", 99 | " insert_update = bm[\"insert-map\"] / insert\n", 100 | " ax.bar(POS[i][0], insert_lock, bar_width, bottom=0, \n", 101 | " label=\"Fetch/\\nLock\", color=STYLES['lock'][0], hatch=STYLES['lock'][1])\n", 102 | " ax.bar(POS[i][0], insert_update, bar_width, bottom=insert_lock, \n", 103 | " label=\"Map\", color=STYLES['map'][0], hatch=STYLES['map'][1])\n", 104 | " ax.bar(POS[i][0], insert_write, bar_width, bottom=insert_lock + insert_update, \n", 105 | " label=\"PMem\", color=STYLES['pmem'][0], hatch=STYLES['pmem'][1])\n", 106 | " ax.text(POS[i][0], text_y, f\"{insert_op_time}\", ha='center', fontsize=dur_fs, rotation=text_rot)\n", 107 | " \n", 108 | " # GET\n", 109 | " get_map = bm[\"get-map\"] / get\n", 110 | " get_lock = bm[\"get-lock\"] / get\n", 111 | " get_read = bm[\"get-pmem\"] / get\n", 112 | " ax.bar(POS[i][1], get_lock, bar_width, bottom=0, \n", 113 | " color=STYLES['lock'][0], hatch=STYLES['lock'][1])\n", 114 | " ax.bar(POS[i][1], get_map, bar_width, bottom=get_lock, \n", 115 | " color=STYLES['map'][0], hatch=STYLES['map'][1])\n", 116 | " ax.bar(POS[i][1], get_read, bar_width, bottom=get_map + get_lock,\n", 117 | " color=STYLES['pmem'][0], hatch=STYLES['pmem'][1])\n", 118 | " ax.text(POS[i][1], text_y, f\"{get_op_time}\", ha='center', fontsize=dur_fs, rotation=text_rot)\n", 119 | " \n", 120 | " # UPDATE\n", 121 | " update_map = bm[\"update-map\"] / update\n", 122 | " update_lock = bm[\"update-lock\"] / update\n", 123 | " update_modify = bm[\"update-pmem\"] / update\n", 124 | " ax.bar(POS[i][2], update_lock, bar_width, bottom=0, \n", 125 | " color=STYLES['lock'][0], hatch=STYLES['lock'][1])\n", 126 | " ax.bar(POS[i][2], update_map, bar_width, bottom=update_lock, \n", 127 | " color=STYLES['map'][0], hatch=STYLES['map'][1])\n", 128 | " ax.bar(POS[i][2], update_modify, bar_width, bottom=update_map + update_lock,\n", 129 | " color=STYLES['pmem'][0], hatch=STYLES['pmem'][1])\n", 130 | " ax.text(POS[i][2], text_y, f\"{update_op_time}\", ha='center', fontsize=dur_fs, rotation=text_rot)\n", 131 | "\n", 132 | " \n", 133 | " # DELETE\n", 134 | " delete_lock = bm[\"delete-lock\"] / delete\n", 135 | " delete_write = bm[\"delete-pmem\"] / delete\n", 136 | " delete_update = bm[\"delete-map\"] / delete\n", 137 | " ax.bar(POS[i][3], delete_lock, bar_width, bottom=0, \n", 138 | " color=STYLES['lock'][0], hatch=STYLES['lock'][1])\n", 139 | " ax.bar(POS[i][3], delete_update, bar_width, bottom=delete_lock, \n", 140 | " color=STYLES['map'][0], hatch=STYLES['map'][1])\n", 141 | " ax.bar(POS[i][3], delete_write, bar_width, bottom=delete_lock + delete_update,\n", 142 | " color=STYLES['pmem'][0], hatch=STYLES['pmem'][1])\n", 143 | " ax.text(POS[i][3], text_y, f\"{delete_op_time}\", ha='center', fontsize=dur_fs, rotation=text_rot)\n", 144 | " \n", 145 | "\n", 146 | "ax.set_xticks([r + (0.0 * bar_width) for r in st_pos])\n", 147 | "ax.xaxis.set_tick_params(pad=1)\n", 148 | "ax.set_xticklabels([\"PUT\", \"GET\", \"UPDATE\", \"DELETE\"], fontsize=18, rotation=30)\n", 149 | "\n", 150 | "ax.set_yticks([0, 0.2, 0.4, 0.6, 0.8, 1])\n", 151 | "# ax.set_yticklabels([\"\", 0.2, \"\", 0.6, \"\", 1])\n", 152 | "\n", 153 | "ax.set_ylabel(\"Normalized dur./op\", fontsize=20)\n", 154 | "ax.yaxis.set_label_coords(-0.28, 0.45)\n", 155 | "\n", 156 | "ax.text(5, 0.9, \"←avg. dur.\\nin $µs$\", ha='center', fontsize=dur_fs + 2)\n", 157 | "\n", 158 | "# Put a legend below current axis\n", 159 | "handles, labels = ax.get_legend_handles_labels()\n", 160 | "ax.legend(handles[::-1], labels[::-1], loc='center right', bbox_to_anchor=(1.74, 0.4), \n", 161 | " ncol=1, frameon=False, framealpha=1,\n", 162 | " fontsize=18, columnspacing=0.3, handletextpad=0.2, labelspacing=0.3,\n", 163 | " handlelength=1.8, borderpad=0.1)\n", 164 | "\n", 165 | "for tick in ax.yaxis.get_major_ticks():\n", 166 | " tick.label.set_fontsize(18)\n", 167 | "\n", 168 | "plt.tick_params(axis='x', bottom=False)\n", 169 | "ax.set_axisbelow(True)\n", 170 | "ax.grid(axis='y', which='major')\n", 171 | "hide_border(ax)\n", 172 | "\n", 173 | "fig.savefig('charts/breakdown_half.pdf', bbox_inches='tight')\n", 174 | "fig.savefig('charts/breakdown_half.svg', bbox_inches='tight')" 175 | ] 176 | } 177 | ], 178 | "metadata": { 179 | "kernelspec": { 180 | "display_name": "Python 3", 181 | "language": "python", 182 | "name": "python3" 183 | }, 184 | "language_info": { 185 | "codemirror_mode": { 186 | "name": "ipython", 187 | "version": 3 188 | }, 189 | "file_extension": ".py", 190 | "mimetype": "text/x-python", 191 | "name": "python", 192 | "nbconvert_exporter": "python", 193 | "pygments_lexer": "ipython3", 194 | "version": "3.9.1" 195 | } 196 | }, 197 | "nbformat": 4, 198 | "nbformat_minor": 4 199 | } 200 | -------------------------------------------------------------------------------- /benchmark/fixtures/utree_fixture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common_fixture.hpp" 4 | #include "../benchmark.hpp" 5 | 6 | #ifndef UTREE_KEY_T 7 | #define UTREE_KEY_T viper::kv_bm::KeyType16 8 | #endif 9 | 10 | #ifndef UTREE_VALUE_T 11 | #define UTREE_VALUE_T viper::kv_bm::ValueType200 12 | #endif 13 | 14 | #include "utree.hpp" 15 | #include "viper/cceh.hpp" 16 | 17 | namespace viper::kv_bm { 18 | 19 | template 20 | class UTreeFixture : public BaseFixture { 21 | public: 22 | static constexpr bool IS_YCSBABLE = std::is_same_v; 23 | 24 | void InitMap(const uint64_t num_prefill_inserts, const bool re_init) final; 25 | void DeInitMap() final; 26 | uint64_t setup_and_insert(uint64_t start_idx, uint64_t end_idx) final; 27 | uint64_t setup_and_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates); 28 | uint64_t setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds); 29 | uint64_t setup_and_delete(uint64_t start_idx, uint64_t end_idx, uint64_t num_deletes); 30 | uint64_t run_ycsb(uint64_t start_idx, uint64_t end_idx, const std::vector& data, 31 | hdr_histogram* hdr) final; 32 | uint64_t insert(uint64_t start_idx, uint64_t end_idx) final; 33 | void prefill_ycsb(const std::vector& data) override; 34 | 35 | protected: 36 | std::unique_ptr utree_; 37 | std::string pmem_pool_name_; 38 | bool map_initialized_ = false; 39 | }; 40 | 41 | template 42 | void UTreeFixture::InitMap(const uint64_t num_prefill_inserts, const bool re_init) { 43 | if (map_initialized_ && !re_init) { 44 | return; 45 | } 46 | 47 | pmem_pool_name_ = random_file(DB_PMEM_DIR); 48 | utree_ = std::make_unique(pmem_pool_name_); 49 | prefill(num_prefill_inserts); 50 | map_initialized_ = true; 51 | } 52 | 53 | template 54 | void UTreeFixture::DeInitMap() { 55 | utree_ = nullptr; 56 | pmempool_rm(pmem_pool_name_.c_str(), 0); 57 | map_initialized_ = false; 58 | } 59 | 60 | template 61 | uint64_t UTreeFixture::insert(uint64_t start_idx, uint64_t end_idx) { 62 | uint64_t insert_counter = 0; 63 | for (uint64_t pos = start_idx; pos < end_idx; ++pos) { 64 | const KeyT db_key{pos}; 65 | const ValueT value{pos}; 66 | insert_counter += utree_->insert(db_key, value); 67 | } 68 | return insert_counter; 69 | } 70 | 71 | template <> 72 | uint64_t UTreeFixture::insert(uint64_t start_idx, uint64_t end_idx) { 73 | throw std::runtime_error("not supported"); 74 | } 75 | 76 | template 77 | uint64_t UTreeFixture::setup_and_insert(uint64_t start_idx, uint64_t end_idx) { 78 | return insert(start_idx, end_idx); 79 | } 80 | 81 | template 82 | uint64_t UTreeFixture::setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) { 83 | std::random_device rnd{}; 84 | auto rnd_engine = std::default_random_engine(rnd()); 85 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 86 | 87 | uint64_t found_counter = 0; 88 | for (uint64_t i = 0; i < num_finds; ++i) { 89 | const uint64_t key = distrib(rnd_engine); 90 | ValueT value; 91 | const bool found = utree_->search(key, &value); 92 | found_counter += found && (value.data[0] == key); 93 | } 94 | return found_counter; 95 | } 96 | 97 | template <> 98 | uint64_t UTreeFixture::setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) { 99 | throw std::runtime_error("not supported"); 100 | } 101 | 102 | template 103 | uint64_t UTreeFixture::setup_and_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates) { 104 | std::random_device rnd{}; 105 | auto rnd_engine = std::default_random_engine(rnd()); 106 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 107 | 108 | uint64_t update_counter = 0; 109 | for (uint64_t i = 0; i < num_updates; ++i) { 110 | const uint64_t key = distrib(rnd_engine); 111 | const KeyT db_key{key}; 112 | ValueT value; 113 | bool found = utree_->search(db_key, &value); 114 | if (found) { 115 | value.update_value(); 116 | update_counter += utree_->insert(db_key, value); 117 | } 118 | } 119 | return update_counter; 120 | } 121 | 122 | template <> 123 | uint64_t UTreeFixture::setup_and_update(uint64_t, uint64_t, uint64_t) { 124 | throw std::runtime_error("not supported"); 125 | } 126 | 127 | template 128 | uint64_t UTreeFixture::setup_and_delete(uint64_t start_idx, uint64_t end_idx, uint64_t num_deletes) { 129 | std::random_device rnd{}; 130 | auto rnd_engine = std::default_random_engine(rnd()); 131 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 132 | 133 | const size_t prefills_per_thread = (100'000'000 / num_util_threads_) + 1; 134 | 135 | uint64_t delete_counter = 0; 136 | for (uint64_t i = 0; i < num_deletes; ++i) { 137 | const uint64_t key = distrib(rnd_engine); 138 | if (key % prefills_per_thread == 0) { 139 | continue; 140 | } 141 | const KeyT db_key{key}; 142 | delete_counter += utree_->remove(db_key); 143 | } 144 | return delete_counter; 145 | } 146 | 147 | template <> 148 | uint64_t UTreeFixture::setup_and_delete(uint64_t, uint64_t, uint64_t) { 149 | throw std::runtime_error("not supported"); 150 | } 151 | 152 | 153 | template 154 | uint64_t UTreeFixture::run_ycsb(uint64_t, uint64_t, const std::vector&, hdr_histogram*) { 155 | throw std::runtime_error{"YCSB not implemented for non-ycsb key/value types."}; 156 | } 157 | 158 | template <> 159 | uint64_t UTreeFixture::run_ycsb(uint64_t start_idx, 160 | uint64_t end_idx, const std::vector& data, hdr_histogram* hdr) { 161 | #ifdef YCSB_BM 162 | ValueType200 value; 163 | const ValueType200 null_value{0ul}; 164 | std::chrono::high_resolution_clock::time_point start; 165 | uint64_t op_count = 0; 166 | for (int op_num = start_idx; op_num < end_idx; ++op_num) { 167 | const ycsb::Record &record = data[op_num]; 168 | 169 | if (hdr != nullptr) { 170 | start = std::chrono::high_resolution_clock::now(); 171 | } 172 | 173 | switch (record.op) { 174 | case ycsb::Record::Op::INSERT: { 175 | utree_->insert(record.key, record.value); 176 | op_count++; 177 | break; 178 | } 179 | case ycsb::Record::Op::GET: { 180 | const bool found = utree_->search(record.key, &value); 181 | op_count += found && (value != null_value); 182 | break; 183 | } 184 | case ycsb::Record::Op::UPDATE: { 185 | const bool found = utree_->search(record.key, &value); 186 | if (found) { 187 | value.update_value(); 188 | utree_->insert(record.key, value); 189 | op_count++; 190 | } 191 | } 192 | default: { 193 | throw std::runtime_error("Unknown operation: " + std::to_string(record.op)); 194 | } 195 | } 196 | 197 | if (hdr == nullptr) { 198 | continue; 199 | } 200 | 201 | const auto end = std::chrono::high_resolution_clock::now(); 202 | const auto duration = std::chrono::duration_cast(end - start); 203 | hdr_record_value(hdr, duration.count()); 204 | } 205 | 206 | return op_count; 207 | #else 208 | throw std::runtime_error("Change UTREE_KEY_T to 8"); 209 | #endif 210 | } 211 | 212 | template 213 | void UTreeFixture::prefill_ycsb(const std::vector& data) { 214 | BaseFixture::prefill_ycsb(data); 215 | } 216 | 217 | } // namespace 218 | -------------------------------------------------------------------------------- /benchmark/fixtures/rocksdb_fixture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "common_fixture.hpp" 6 | #include "rocksdb/db.h" 7 | #include "env_dcpmm.h" 8 | #include "cache_dcpmm.h" 9 | 10 | namespace viper { 11 | namespace kv_bm { 12 | 13 | template 14 | class RocksDbFixture : public BaseFixture { 15 | public: 16 | void InitMap(const uint64_t num_prefill_inserts = 0, const bool re_init = true) override; 17 | 18 | void DeInitMap() override; 19 | 20 | uint64_t insert(uint64_t start_idx, uint64_t end_idx) final; 21 | 22 | uint64_t setup_and_insert(uint64_t start_idx, uint64_t end_idx) final; 23 | uint64_t setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) final; 24 | uint64_t setup_and_delete(uint64_t start_idx, uint64_t end_idx, uint64_t num_deletes) final; 25 | uint64_t setup_and_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates) final; 26 | 27 | virtual std::string get_base_dir() = 0; 28 | 29 | protected: 30 | rocksdb::DB* db_; 31 | std::filesystem::path base_dir_; 32 | std::filesystem::path db_dir_; 33 | std::filesystem::path wal_dir_; 34 | bool rocksdb_initialized_; 35 | }; 36 | 37 | template 38 | class DiskRocksDbFixture : public RocksDbFixture { 39 | public: 40 | std::string get_base_dir() override; 41 | }; 42 | 43 | template 44 | class PmemRocksDbFixture : public RocksDbFixture { 45 | public: 46 | std::string get_base_dir() override; 47 | }; 48 | 49 | template 50 | void RocksDbFixture::InitMap(uint64_t num_prefill_inserts, const bool re_init) { 51 | if (rocksdb_initialized_ && !re_init) { 52 | return; 53 | } 54 | 55 | const std::string config_file = CONFIG_DIR + std::string("rocksdb.conf"); 56 | rocksdb::Options options; 57 | // rocksdb::Env* env = rocksdb::Env::Default(); 58 | rocksdb::Env* env = rocksdb::NewDCPMMEnv(rocksdb::DCPMMEnvOptions()); 59 | 60 | // std::vector cfd; 61 | // rocksdb::Status s = rocksdb::LoadOptionsFromFile(config_file, env, &options, &cfd); 62 | // auto cache = rocksdb::NewLRUCache(671088640); 63 | // rocksdb::Status s = rocksdb::LoadOptionsFromFile(config_file, env, &options, &cfd, false, &cache); 64 | // if (!s.ok()) { 65 | // throw std::runtime_error("Could not load RocksDb config file."); 66 | // } 67 | 68 | base_dir_ = get_base_dir(); 69 | db_dir_ = random_file(base_dir_); 70 | wal_dir_ = db_dir_ / "rocksdb-wal"; 71 | 72 | auto cache = rocksdb::NewLRUCache(4294967296); 73 | rocksdb::BlockBasedTableOptions table_options; 74 | table_options.block_cache = cache; 75 | options.table_factory.reset(rocksdb::NewBlockBasedTableFactory(table_options)); 76 | 77 | options.dcpmm_kvs_mmapped_file_fullpath = db_dir_ / "rocksdb.value"; 78 | options.dcpmm_kvs_mmapped_file_size = 50 * ONE_GB; 79 | options.dcpmm_kvs_value_thres = 64; 80 | options.dcpmm_compress_value = true; 81 | options.wal_dir = wal_dir_; 82 | options.create_if_missing = true; 83 | options.error_if_exists = true; 84 | options.enable_write_thread_adaptive_yield = false; 85 | options.disable_auto_compactions = false; 86 | options.max_background_compactions = 32; 87 | options.max_background_flushes = 4; 88 | options.enable_pipelined_write = true; 89 | options.allow_concurrent_memtable_write = true; 90 | options.use_direct_io_for_flush_and_compaction = true; 91 | options.target_file_size_base = 67108864; 92 | 93 | rocksdb::Status status = rocksdb::DB::Open(options, db_dir_, &db_); 94 | if (!status.ok()) { 95 | std::cerr << status.ToString() << std::endl; 96 | } 97 | 98 | prefill(num_prefill_inserts); 99 | rocksdb_initialized_ = true; 100 | } 101 | 102 | template 103 | void RocksDbFixture::DeInitMap() { 104 | delete db_; 105 | rocksdb_initialized_ = false; 106 | std::filesystem::remove_all(db_dir_); 107 | std::filesystem::remove_all(wal_dir_); 108 | } 109 | 110 | template 111 | uint64_t RocksDbFixture::insert(uint64_t start_idx, uint64_t end_idx) { 112 | uint64_t insert_counter = 0; 113 | const rocksdb::WriteOptions write_options{}; 114 | for (uint64_t key = start_idx; key < end_idx; ++key) { 115 | const KeyT kt{key}; 116 | const ValueT vt{key}; 117 | const rocksdb::Slice db_key{(char*) &kt.data, sizeof(KeyT)}; 118 | const rocksdb::Slice value_str{(char*) &vt.data, sizeof(ValueT)}; 119 | insert_counter += db_->Put(write_options, db_key, value_str).ok(); 120 | } 121 | return insert_counter; 122 | } 123 | 124 | template 125 | uint64_t RocksDbFixture::setup_and_insert(uint64_t start_idx, uint64_t end_idx) { 126 | return insert(start_idx, end_idx); 127 | } 128 | 129 | template 130 | uint64_t RocksDbFixture::setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) { 131 | std::random_device rnd{}; 132 | auto rnd_engine = std::default_random_engine(rnd()); 133 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 134 | 135 | uint64_t found_counter = 0; 136 | const rocksdb::ReadOptions read_options{}; 137 | for (uint64_t i = 0; i < num_finds; ++i) { 138 | std::string value; 139 | 140 | const uint64_t key = distrib(rnd_engine); 141 | const KeyT kt{key}; 142 | const rocksdb::Slice db_key{(char*) &kt.data, sizeof(KeyT)}; 143 | const bool found = db_->Get(read_options, db_key, &value).ok(); 144 | if (found) { 145 | found_counter += ValueT{}.from_str(value).data[0] == key; 146 | } 147 | } 148 | return found_counter; 149 | } 150 | 151 | template 152 | uint64_t RocksDbFixture::setup_and_delete(uint64_t start_idx, uint64_t end_idx, uint64_t num_deletes) { 153 | std::random_device rnd{}; 154 | auto rnd_engine = std::default_random_engine(rnd()); 155 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 156 | 157 | uint64_t delete_counter = 0; 158 | const rocksdb::WriteOptions delete_options{}; 159 | for (uint64_t i = 0; i < num_deletes; ++i) { 160 | const uint64_t key = distrib(rnd_engine); 161 | const KeyT kt{key}; 162 | const rocksdb::Slice db_key{(char*) &kt.data, sizeof(KeyT)}; 163 | delete_counter += db_->Delete(delete_options, db_key).ok(); 164 | } 165 | return delete_counter; 166 | } 167 | template 168 | uint64_t RocksDbFixture::setup_and_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates) { 169 | std::random_device rnd{}; 170 | auto rnd_engine = std::default_random_engine(rnd()); 171 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 172 | 173 | uint64_t update_counter = 0; 174 | const rocksdb::ReadOptions read_options{}; 175 | const rocksdb::WriteOptions write_options{}; 176 | for (uint64_t i = 0; i < num_updates; ++i) { 177 | std::string value; 178 | const uint64_t key = distrib(rnd_engine); 179 | const KeyT kt{key}; 180 | const rocksdb::Slice db_key{(char*) &kt.data, sizeof(KeyT)}; 181 | const bool found = db_->Get(read_options, db_key, &value).ok(); 182 | if (found) { 183 | ValueT new_value{}; 184 | new_value.from_str(value); 185 | new_value.update_value(); 186 | const rocksdb::Slice new_value_str{(char*) &new_value.data, sizeof(ValueT)}; 187 | update_counter += db_->Put(write_options, db_key, new_value_str).ok(); 188 | } 189 | } 190 | return update_counter; 191 | } 192 | 193 | template 194 | std::string DiskRocksDbFixture::get_base_dir() { 195 | return DB_FILE_DIR; 196 | } 197 | 198 | template 199 | std::string PmemRocksDbFixture::get_base_dir() { 200 | return DB_PMEM_DIR + std::string("/rocks"); 201 | } 202 | 203 | } // namespace kv_bm 204 | } // namespace viper 205 | -------------------------------------------------------------------------------- /benchmark/fixtures/crl_fixture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common_fixture.hpp" 4 | #include "../benchmark.hpp" 5 | 6 | #include "crl.hpp" 7 | 8 | namespace viper::kv_bm { 9 | 10 | template 11 | class CrlFixture : public BaseFixture { 12 | public: 13 | void InitMap(const uint64_t num_prefill_inserts, const bool re_init) final; 14 | void DeInitMap() final; 15 | uint64_t setup_and_insert(uint64_t start_idx, uint64_t end_idx) final; 16 | uint64_t setup_and_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates); 17 | uint64_t setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds); 18 | uint64_t setup_and_delete(uint64_t start_idx, uint64_t end_idx, uint64_t num_deletes); 19 | uint64_t run_ycsb(uint64_t start_idx, uint64_t end_idx, const std::vector& data, 20 | hdr_histogram* hdr) final; 21 | uint64_t insert(uint64_t start_idx, uint64_t end_idx) final; 22 | void prefill_ycsb(const std::vector& data) override; 23 | 24 | protected: 25 | std::unique_ptr> crl_store_; 26 | std::string log_pool_name_; 27 | std::string backend_pool_name_; 28 | bool map_initialized_ = false; 29 | }; 30 | 31 | template 32 | void CrlFixture::InitMap(const uint64_t num_prefill_inserts, const bool re_init) { 33 | if (map_initialized_ && !re_init) { 34 | return; 35 | } 36 | 37 | num_util_threads_ = 18; 38 | log_pool_name_ = random_file(DB_PMEM_DIR); 39 | backend_pool_name_ = random_file(DB_PMEM_DIR); 40 | const size_t backend_file_size = 200 * ONE_GB; 41 | crl_store_ = std::make_unique>(log_pool_name_, backend_pool_name_, backend_file_size); 42 | prefill(num_prefill_inserts); 43 | crl_store_->collect_gleaners(); 44 | map_initialized_ = true; 45 | } 46 | 47 | template 48 | void CrlFixture::DeInitMap() { 49 | crl_store_ = nullptr; 50 | map_initialized_ = false; 51 | } 52 | 53 | template 54 | uint64_t CrlFixture::insert(uint64_t start_idx, uint64_t end_idx) { 55 | auto client = crl_store_->get_client(); 56 | uint64_t insert_counter = 0; 57 | for (uint64_t pos = start_idx; pos < end_idx; ++pos) { 58 | const KeyT db_key{pos}; 59 | const ValueT value{pos}; 60 | insert_counter += client.put(db_key, value); 61 | } 62 | return insert_counter; 63 | } 64 | 65 | template <> 66 | uint64_t CrlFixture::insert(uint64_t start_idx, uint64_t end_idx) { 67 | uint64_t insert_counter = 0; 68 | auto client = crl_store_->get_client(); 69 | const std::vector& keys = std::get<0>(var_size_kvs_); 70 | const std::vector& values = std::get<1>(var_size_kvs_); 71 | for (uint64_t key = start_idx; key < end_idx; ++key) { 72 | const std::string& db_key = keys[key]; 73 | const std::string& value = values[key]; 74 | insert_counter += client.put(db_key, value); 75 | } 76 | return insert_counter; 77 | } 78 | 79 | template 80 | uint64_t CrlFixture::setup_and_insert(uint64_t start_idx, uint64_t end_idx) { 81 | return insert(start_idx, end_idx); 82 | } 83 | 84 | template 85 | uint64_t CrlFixture::setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) { 86 | std::random_device rnd{}; 87 | auto rnd_engine = std::default_random_engine(rnd()); 88 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 89 | 90 | auto client = crl_store_->get_read_only_client(); 91 | uint64_t found_counter = 0; 92 | ValueT value; 93 | for (uint64_t i = 0; i < num_finds; ++i) { 94 | const uint64_t key = distrib(rnd_engine); 95 | const KeyT db_key{key}; 96 | const bool found = client.get(db_key, &value); 97 | found_counter += found && (value == ValueT{key}); 98 | } 99 | return found_counter; 100 | } 101 | 102 | template <> 103 | uint64_t CrlFixture::setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) { 104 | std::random_device rnd{}; 105 | auto rnd_engine = std::default_random_engine(rnd()); 106 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 107 | 108 | const std::vector& keys = std::get<0>(var_size_kvs_); 109 | const std::vector& values = std::get<1>(var_size_kvs_); 110 | 111 | auto client = crl_store_->get_read_only_client(); 112 | uint64_t found_counter = 0; 113 | std::string result; 114 | for (uint64_t i = 0; i < num_finds; ++i) { 115 | const uint64_t key = distrib(rnd_engine); 116 | const std::string& db_key = keys[key]; 117 | const std::string& value = values[key]; 118 | const bool found = client.get(db_key, &result); 119 | found_counter += found && (result == value); 120 | } 121 | return found_counter; 122 | } 123 | 124 | template 125 | uint64_t CrlFixture::setup_and_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates) { 126 | std::random_device rnd{}; 127 | auto rnd_engine = std::default_random_engine(rnd()); 128 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 129 | 130 | auto client = crl_store_->get_client(); 131 | uint64_t update_counter = 0; 132 | for (uint64_t i = 0; i < num_updates; ++i) { 133 | const uint64_t key = distrib(rnd_engine); 134 | const KeyT db_key{key}; 135 | ValueT value; 136 | bool found = client.get(db_key, &value); 137 | if (found) { 138 | value.update_value(); 139 | client.put(db_key, value); 140 | update_counter++; 141 | } 142 | } 143 | return update_counter; 144 | } 145 | 146 | template <> 147 | uint64_t CrlFixture::setup_and_update(uint64_t, uint64_t, uint64_t) { 148 | throw std::runtime_error("not supported"); 149 | } 150 | 151 | template 152 | uint64_t CrlFixture::setup_and_delete(uint64_t start_idx, uint64_t end_idx, uint64_t num_deletes) { 153 | std::random_device rnd{}; 154 | auto rnd_engine = std::default_random_engine(rnd()); 155 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 156 | 157 | auto client = crl_store_->get_client(); 158 | uint64_t delete_counter = 0; 159 | for (uint64_t i = 0; i < num_deletes; ++i) { 160 | const uint64_t key = distrib(rnd_engine); 161 | const KeyT db_key{key}; 162 | delete_counter += client.remove(db_key); 163 | } 164 | return delete_counter; 165 | } 166 | 167 | template <> 168 | uint64_t CrlFixture::setup_and_delete(uint64_t, uint64_t, uint64_t) { 169 | throw std::runtime_error("not supported"); 170 | } 171 | 172 | 173 | template 174 | uint64_t CrlFixture::run_ycsb(uint64_t, uint64_t, const std::vector&, hdr_histogram*) { 175 | throw std::runtime_error{"YCSB not implemented for non-ycsb key/value types."}; 176 | } 177 | 178 | template <> 179 | uint64_t CrlFixture::run_ycsb(uint64_t start_idx, uint64_t end_idx, const std::vector& data, hdr_histogram* hdr) { 180 | ValueType200 value; 181 | const ValueType200 null_value{0ul}; 182 | std::chrono::high_resolution_clock::time_point start; 183 | 184 | auto client = crl_store_->get_client(); 185 | uint64_t op_count = 0; 186 | for (int op_num = start_idx; op_num < end_idx; ++op_num) { 187 | const ycsb::Record& record = data[op_num]; 188 | 189 | if (hdr != nullptr) { 190 | start = std::chrono::high_resolution_clock::now(); 191 | } 192 | 193 | switch (record.op) { 194 | case ycsb::Record::Op::INSERT: { 195 | client.put(record.key, record.value); 196 | op_count++; 197 | break; 198 | } 199 | case ycsb::Record::Op::GET: { 200 | const bool found = client.get(record.key, &value); 201 | op_count += found && (value != null_value); 202 | break; 203 | } 204 | case ycsb::Record::Op::UPDATE: { 205 | const bool found = client.get(record.key, &value); 206 | if (found) { 207 | value.update_value(); 208 | client.put(record.key, value); 209 | op_count++; 210 | } 211 | } 212 | default: { 213 | throw std::runtime_error("Unknown operation: " + std::to_string(record.op)); 214 | } 215 | } 216 | 217 | if (hdr == nullptr) { 218 | continue; 219 | } 220 | 221 | const auto end = std::chrono::high_resolution_clock::now(); 222 | const auto duration = std::chrono::duration_cast(end - start); 223 | hdr_record_value(hdr, duration.count()); 224 | } 225 | 226 | return op_count; 227 | } 228 | 229 | template 230 | void CrlFixture::prefill_ycsb(const std::vector& data) { 231 | BaseFixture::prefill_ycsb(data); 232 | crl_store_->collect_gleaners(); 233 | } 234 | 235 | } // namespace 236 | -------------------------------------------------------------------------------- /benchmark/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18) 2 | 3 | include(ExternalProject) 4 | include(FetchContent) 5 | 6 | cmake_policy(SET CMP0077 NEW) 7 | 8 | SET( 9 | BASE_BENCHMARK_FILES 10 | 11 | benchmark.cpp 12 | benchmark.hpp 13 | fixtures/common_fixture.cpp 14 | fixtures/common_fixture.hpp 15 | fixtures/viper_fixture.hpp 16 | ) 17 | 18 | SET( 19 | ALL_SYSTEMS_BENCHMARK_FILES 20 | 21 | ${BASE_BENCHMARK_FILES} 22 | fixtures/rocksdb_fixture.hpp 23 | fixtures/faster_fixture.hpp 24 | fixtures/pmem_kv_fixture.hpp 25 | fixtures/cceh_fixture.hpp 26 | fixtures/dash_fixture.hpp 27 | fixtures/utree.hpp 28 | fixtures/utree_fixture.hpp 29 | fixtures/crl.hpp 30 | fixtures/crl_fixture.hpp 31 | fixtures/tbb_fixture.hpp 32 | ) 33 | 34 | cmake_host_system_information(RESULT host_name QUERY HOSTNAME) 35 | message(STATUS "Running on host: ${host_name}") 36 | if ("${host_name}" STREQUAL "nvram-01") 37 | message(STATUS "Known host: nvram01") 38 | add_definitions(-DNVRAM01) 39 | elseif("${host_name}" STREQUAL "nvram-02") 40 | message(STATUS "Known host: nvram02") 41 | add_definitions(-DNVRAM02) 42 | else() 43 | message(STATUS "Unknown host: ${host_name}") 44 | endif() 45 | 46 | # GOOGLE BENCHMARK 47 | FetchContent_Declare( 48 | google_benchmark 49 | GIT_REPOSITORY https://github.com/google/benchmark.git 50 | GIT_TAG v1.5.2 51 | ) 52 | set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "Suppressing benchmark's tests" FORCE) 53 | FetchContent_MakeAvailable(google_benchmark) 54 | 55 | # PMDK 56 | set(PMDK_INCLUDE_HINTS ${VIPER_PMDK_PATH}/include) 57 | set(PMDK_LIB_HINTS ${VIPER_PMDK_PATH}/lib) 58 | 59 | find_path(PMDK_INCLUDE_DIRS libpmem.h HINTS ${PMDK_INCLUDE_HINTS}) 60 | find_library(PMDK_LIBRARIES NAMES pmem libpmem HINTS ${PMDK_LIB_HINTS}) 61 | 62 | set(PMDK_FOUND true) 63 | if (NOT PMDK_INCLUDE_DIRS OR "${PMDK_INCLUDE_DIRS}" STREQUAL "") 64 | message(FATAL_ERROR "libpmem include directory not found in default locations or custom path (${PMDK_INCLUDE_HINTS}).") 65 | endif () 66 | if (NOT PMDK_LIBRARIES OR "${PMDK_LIBRARIES}" STREQUAL "") 67 | message(FATAL_ERROR "libmem not found in default locations or custom path (${PMDK_LIB_HINTS}).") 68 | endif () 69 | 70 | message(STATUS "Found PMDK. Including ${PMDK_INCLUDE_DIRS} and linking ${PMDK_LIBRARIES}") 71 | include_directories(${PMDK_INCLUDE_DIRS}) 72 | 73 | # libpmemobj + libpmempool 74 | find_library(PMEMOBJ_LIBRARIES NAMES pmemobj libpmemobj HINTS ${PMDK_LIB_HINTS}) 75 | find_library(PMEMPOOL_LIBRARIES NAMES pmempool libpmempool HINTS ${PMDK_LIB_HINTS}) 76 | 77 | # libpmemobj++ 78 | # Use -DLIBPMEMOBJ++_PATH=/path/to/libpmemobj++ for custom path 79 | set(LIBPMEMOBJ++_PATH "/scratch/pmem/libpmemobj-cpp" CACHE PATH 80 | "Path to custom libpmemobj++ install directory") 81 | include_directories(${LIBPMEMOBJ++_PATH}/include) 82 | 83 | # TBB 84 | set(TBB_TEST OFF CACHE INTERNAL "") 85 | set(TBB_EXAMPLES OFF CACHE INTERNAL "") 86 | FetchContent_Declare( 87 | oneTBB 88 | GIT_REPOSITORY https://github.com/oneapi-src/oneTBB.git 89 | GIT_TAG v2021.1.1 90 | ) 91 | FetchContent_MakeAvailable(oneTBB) 92 | include_directories(${oneTBB_SOURCE_DIR}/include) 93 | 94 | 95 | # PmemKV 96 | set(OLD_CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH}) 97 | set(CMAKE_PREFIX_PATH "${LIBPMEMOBJ++_PATH};${VIPER_PMDK_PATH}") 98 | 99 | cmake_policy(SET CMP0077 NEW) 100 | set(BUILD_DOC OFF CACHE BOOL "" FORCE) 101 | set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) 102 | set(BUILD_TESTS OFF CACHE BOOL "" FORCE) 103 | set(BUILD_JSON_CONFIG OFF CACHE BOOL "" FORCE) 104 | set(ENGINE_VCMAP OFF CACHE BOOL "" FORCE) 105 | set(ENGINE_VSMAP OFF CACHE BOOL "" FORCE) 106 | set(ENGINE_CACHING OFF CACHE BOOL "" FORCE) 107 | set(ENGINE_STREE OFF CACHE BOOL "" FORCE) 108 | set(ENGINE_TREE3 OFF CACHE BOOL "" FORCE) 109 | FetchContent_Declare( 110 | pmemkv 111 | GIT_REPOSITORY https://github.com/pmem/pmemkv.git 112 | GIT_TAG 1.4 113 | ) 114 | FetchContent_MakeAvailable(pmemkv) 115 | include_directories(${pmemkv_SOURCE_DIR}/src) 116 | set(CMAKE_PREFIX_PATH ${OLD_CMAKE_PREFIX_PATH}) 117 | 118 | 119 | # PMEM-ROCKSDB 120 | set(PMEM_ROCKSDB_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/pmem-rocksdb") 121 | ExternalProject_Add( 122 | pmem_rocksdb 123 | GIT_REPOSITORY https://github.com/lawben/pmem-rocksdb.git 124 | GIT_TAG 8352e95 125 | PREFIX ${PMEM_ROCKSDB_PREFIX} 126 | CONFIGURE_COMMAND "" 127 | UPDATE_COMMAND "" 128 | INSTALL_COMMAND "" 129 | UPDATE_DISCONNECTED 1 130 | LOG_BUILD ON 131 | BUILD_IN_SOURCE 1 132 | 133 | # Use DRAM cache for now 134 | # BUILD_COMMAND $(MAKE) shared_lib -j ROCKSDB_BC_ON_DCPMM=1 ROCKSDB_KVS_ON_DCPMM=1 ROCKSDB_WAL_ON_DCPMM=1 DISABLE_WARNING_AS_ERROR=1 135 | BUILD_COMMAND $(MAKE) shared_lib -j ROCKSDB_KVS_ON_DCPMM=1 ROCKSDB_WAL_ON_DCPMM=1 DISABLE_WARNING_AS_ERROR=1 136 | ) 137 | # Use DRAM cache for now 138 | #add_definitions(-DBC_ON_DCPMM=1 -DKVS_ON_DCPMM=1 -DWAL_ON_DCPMM=1) 139 | add_definitions(-DKVS_ON_DCPMM=1 -DWAL_ON_DCPMM=1) 140 | 141 | link_directories(${PMEM_ROCKSDB_PREFIX}/src/pmem_rocksdb) 142 | include_directories(${PMEM_ROCKSDB_PREFIX}/src/pmem_rocksdb/include) 143 | include_directories(${PMEM_ROCKSDB_PREFIX}/src/pmem_rocksdb/dcpmm) 144 | 145 | 146 | # Dash 147 | FetchContent_Declare( 148 | epoch_reclaimer 149 | GIT_REPOSITORY https://github.com/XiangpengHao/epoch-reclaimer.git 150 | GIT_TAG master 151 | ) 152 | if (NOT epoch_reclaimer_POPULATED) 153 | FetchContent_Populate(epoch_reclaimer) 154 | endif () 155 | include_directories(${epoch_reclaimer_SOURCE_DIR}) 156 | add_definitions(-DPMEM) 157 | FetchContent_Declare( 158 | dash 159 | GIT_REPOSITORY https://github.com/baotonglu/dash.git 160 | GIT_TAG 7e1551f 161 | ) 162 | if (NOT dash_POPULATED) 163 | FetchContent_Populate(dash) 164 | endif () 165 | include_directories(${dash_SOURCE_DIR}/src) 166 | execute_process(COMMAND sed -i -e "s/, (void\\*)pool_addr//g" ${dash_SOURCE_DIR}/src/allocator.h) 167 | execute_process(COMMAND sed -i -e "s/pmemobj_create_addr/pmemobj_create/g" ${dash_SOURCE_DIR}/src/allocator.h) 168 | execute_process(COMMAND sed -i -e "s/pmemobj_open_addr/pmemobj_open/g" ${dash_SOURCE_DIR}/src/allocator.h) 169 | execute_process(COMMAND sed -i -e "s#std::cout << \"Directory_Doubling#//#g" ${dash_SOURCE_DIR}/src/ex_finger.h) 170 | 171 | # FASTER 172 | FetchContent_Declare( 173 | faster 174 | GIT_REPOSITORY https://github.com/lawben/faster.git 175 | GIT_TAG 08bd5c4 176 | SOURCE_SUBDIR cc 177 | ) 178 | FetchContent_MakeAvailable(faster) 179 | include_directories(${faster_SOURCE_DIR}/cc/src) 180 | 181 | # HdrHistogram 182 | set(HDR_HISTOGRAM_BUILD_PROGRAMS OFF CACHE INTERNAL "") 183 | FetchContent_Declare( 184 | hdr_histogram 185 | GIT_REPOSITORY https://github.com/HdrHistogram/HdrHistogram_c.git 186 | GIT_TAG 0.11.2 187 | ) 188 | FetchContent_MakeAvailable(hdr_histogram) 189 | 190 | set(PMEM_LIBS ${PMDK_LIBRARIES} ${PMEMOBJ_LIBRARIES} ${PMEMPOOL_LIBRARIES}) 191 | 192 | if (CMAKE_BUILD_TYPE MATCHES Release) 193 | message(STATUS "BUILDING BENCHMARKS IN RELEASE") 194 | add_compile_options(-O3 -Ofast -mtune=native) 195 | else() 196 | message(WARNING "Building benchmarks in ${CMAKE_BUILD_TYPE}. This may impact performance!") 197 | endif() 198 | 199 | add_executable(all_ops_bm all_ops_benchmark.cpp ${ALL_SYSTEMS_BENCHMARK_FILES}) 200 | target_link_libraries(all_ops_bm viper ${PMEM_LIBS}) 201 | target_link_libraries(all_ops_bm benchmark pmemkv faster tbb uuid aio hdr_histogram_static) 202 | # TODO: fix rocksdb build 203 | #add_dependencies(all_ops_bm pmem_rocksdb) 204 | #target_link_libraries(all_ops_bm rocksdb snappy) 205 | set_target_properties(all_ops_bm PROPERTIES LINKER_LANGUAGE CXX) 206 | 207 | add_executable(update_bm update_bm.cpp ${BASE_BENCHMARK_FILES}) 208 | target_link_libraries(update_bm viper ${PMEM_LIBS}) 209 | target_link_libraries(update_bm benchmark hdr_histogram_static) 210 | set_target_properties(update_bm PROPERTIES LINKER_LANGUAGE CXX) 211 | 212 | add_executable(recovery_bm recovery_bm.cpp ${BASE_BENCHMARK_FILES}) 213 | target_link_libraries(recovery_bm viper ${PMEM_LIBS}) 214 | target_link_libraries(recovery_bm benchmark hdr_histogram_static) 215 | set_target_properties(recovery_bm PROPERTIES LINKER_LANGUAGE CXX) 216 | 217 | add_executable(reclaim_bm reclaim_bm.cpp ${BASE_BENCHMARK_FILES}) 218 | target_link_libraries(reclaim_bm viper ${PMEM_LIBS}) 219 | target_link_libraries(reclaim_bm benchmark hdr_histogram_static) 220 | set_target_properties(reclaim_bm PROPERTIES LINKER_LANGUAGE CXX) 221 | 222 | add_executable(kv_size_bm key_value_size_bm.cpp ${BASE_BENCHMARK_FILES}) 223 | target_link_libraries(kv_size_bm viper ${PMEM_LIBS}) 224 | target_link_libraries(kv_size_bm benchmark faster tbb uuid pmemkv aio hdr_histogram_static) 225 | set_target_properties(kv_size_bm PROPERTIES LINKER_LANGUAGE CXX) 226 | 227 | add_executable(variable_size_bm variable_size_bm.cpp ${BASE_BENCHMARK_FILES}) 228 | target_link_libraries(variable_size_bm viper ${PMEM_LIBS}) 229 | target_link_libraries(variable_size_bm benchmark faster uuid aio tbb pmemkv hdr_histogram_static) 230 | set_target_properties(variable_size_bm PROPERTIES LINKER_LANGUAGE CXX) 231 | 232 | add_executable(ycsb_bm ycsb_bm.cpp fixtures/ycsb_common.cpp fixtures/ycsb_common.hpp ${ALL_SYSTEMS_BENCHMARK_FILES}) 233 | target_link_libraries(ycsb_bm viper ${PMEM_LIBS}) 234 | target_link_libraries(ycsb_bm benchmark faster pmemkv tbb uuid aio hdr_histogram_static) 235 | set_target_properties(ycsb_bm PROPERTIES LINKER_LANGUAGE CXX) 236 | 237 | add_executable(latency_bw_bm latency_bw_bm.cpp ${BASE_BENCHMARK_FILES}) 238 | target_link_libraries(latency_bw_bm viper ${PMEM_LIBS}) 239 | target_link_libraries(latency_bw_bm benchmark hdr_histogram_static) 240 | target_compile_options(latency_bw_bm PRIVATE -march=native) 241 | set_target_properties(latency_bw_bm PROPERTIES LINKER_LANGUAGE CXX) 242 | -------------------------------------------------------------------------------- /benchmark/fixtures/common_fixture.cpp: -------------------------------------------------------------------------------- 1 | #include "common_fixture.hpp" 2 | #include "ycsb_common.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace viper::kv_bm { 9 | 10 | std::string random_file(const std::filesystem::path& base_dir) { 11 | if (!std::filesystem::exists(base_dir)) { 12 | if (!std::filesystem::create_directories(base_dir)) { 13 | throw std::runtime_error{"Could not create dir: " + base_dir.string() + "\n"}; 14 | } 15 | } 16 | std::string str("abcdefghijklmnopqrstuvwxyz"); 17 | std::random_device rd; 18 | std::mt19937 generator(rd()); 19 | std::shuffle(str.begin(), str.end(), generator); 20 | std::string file_name = str.substr(0, 15) + ".file"; 21 | std::filesystem::path file{file_name}; 22 | return base_dir / file; 23 | } 24 | 25 | VarSizeKVs BaseFixture::var_size_kvs_; 26 | 27 | void BaseFixture::log_find_count(benchmark::State& state, uint64_t num_found, uint64_t num_expected) { 28 | state.counters["found"] = num_found; 29 | if (num_found != num_expected) { 30 | std::cerr << " DID NOT FIND ALL ENTRIES (" + std::to_string(num_found) 31 | + "/" + std::to_string(num_expected) + ")\n"; 32 | } 33 | } 34 | 35 | template 36 | void BaseFixture::prefill_internal(const size_t num_prefills, PrefillFn prefill_fn) { 37 | #ifndef NDEBUG 38 | std::cout << "START PREFILL." << std::endl; 39 | const auto start = std::chrono::high_resolution_clock::now(); 40 | #endif 41 | cpu_set_t cpuset_before; 42 | pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset_before); 43 | set_cpu_affinity(); 44 | 45 | std::vector prefill_threads{}; 46 | const size_t num_prefills_per_thread = (num_prefills / num_util_threads_) + 1; 47 | for (size_t thread_num = 0; thread_num < num_util_threads_; ++thread_num) { 48 | const size_t start_key = thread_num * num_prefills_per_thread; 49 | const size_t end_key = std::min(start_key + num_prefills_per_thread, num_prefills); 50 | prefill_threads.emplace_back([=]() { 51 | set_cpu_affinity(thread_num); 52 | prefill_fn(start_key, end_key); 53 | }); 54 | } 55 | 56 | for (std::thread& thread : prefill_threads) { 57 | thread.join(); 58 | } 59 | 60 | pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset_before); 61 | #ifndef NDEBUG 62 | const auto end = std::chrono::high_resolution_clock::now(); 63 | const auto duration = std::chrono::duration_cast(end - start).count(); 64 | std::cout << "PREFILL DURATION: " << duration << " s (~" << (num_prefills / (duration + 0.5) / 1e6) << "M/s)" << std::endl; 65 | #endif 66 | } 67 | 68 | void BaseFixture::prefill(const size_t num_prefills) { 69 | if (num_prefills == 0) { 70 | return; 71 | } 72 | 73 | auto prefill_fn = [this](const size_t start, const size_t end) { 74 | this->insert(start, end); 75 | }; 76 | 77 | prefill_internal(num_prefills, prefill_fn); 78 | } 79 | 80 | void BaseFixture::prefill_ycsb(const std::vector& data) { 81 | const size_t num_prefills = data.size(); 82 | auto prefill_fn = [&](const size_t start, const size_t end) { 83 | this->run_ycsb(start, end, data, nullptr); 84 | }; 85 | 86 | prefill_internal(num_prefills, prefill_fn); 87 | } 88 | 89 | void BaseFixture::generate_strings(size_t num_strings, size_t key_size, size_t value_size) { 90 | static const char alphabet[] = 91 | "abcdefghijklmnopqrstuvwxyz" 92 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 93 | "0123456789"; 94 | 95 | std::vector& keys = std::get<0>(var_size_kvs_); 96 | if (keys.size() > 0) { 97 | // Data has been generated already. 98 | #ifndef NDEBUG 99 | std::cout << "SKIPPING STRING GENERATION..." << std::endl; 100 | #endif 101 | return; 102 | } 103 | 104 | cpu_set_t cpuset_before; 105 | pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset_before); 106 | set_cpu_affinity(); 107 | 108 | const auto start = std::chrono::high_resolution_clock::now(); 109 | 110 | auto record_gen_fn = [](std::vector& records, size_t start, size_t end, size_t record_size) { 111 | std::seed_seq seed{start}; 112 | std::mt19937_64 rng{seed}; 113 | std::normal_distribution<> record_length_dist(record_size, 0.2 * record_size); 114 | std::uniform_int_distribution<> dist(0, 61); 115 | 116 | for (auto i = start; i < end; ++i) { 117 | size_t record_len = record_length_dist(rng); 118 | record_len = std::min(record_len, 2 * record_size); 119 | record_len = std::max(1ul, record_len); 120 | std::string str; 121 | str.reserve(record_len); 122 | std::generate_n(std::back_inserter(str), record_len, [&]() { return alphabet[dist(rng)]; }); 123 | records[i] = std::move(str); 124 | } 125 | }; 126 | 127 | keys.resize(num_strings); 128 | std::vector& values = std::get<1>(var_size_kvs_); 129 | values.resize(num_strings); 130 | 131 | std::vector generator_threads{}; 132 | const size_t num_strings_per_thread = (num_strings / NUM_UTIL_THREADS) + 1; 133 | for (size_t thread_num = 0; thread_num < NUM_UTIL_THREADS; ++thread_num) { 134 | const size_t start_i = thread_num * num_strings_per_thread; 135 | const size_t end_i = std::min(start_i + num_strings_per_thread, num_strings); 136 | generator_threads.emplace_back([=, &keys, &values]() { 137 | set_cpu_affinity(thread_num); 138 | record_gen_fn(keys, start_i, end_i, key_size); 139 | record_gen_fn(values, start_i, end_i, value_size); 140 | }); 141 | } 142 | 143 | for (std::thread& thread : generator_threads) { 144 | thread.join(); 145 | } 146 | 147 | pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset_before); 148 | 149 | std::unordered_set unique_keys{}; 150 | unique_keys.reserve(keys.size()); 151 | for (int i = 0; i < keys.size(); ++i) { 152 | std::string* key = &keys[i]; 153 | while (unique_keys.find(*key) != unique_keys.end()) { 154 | record_gen_fn(keys, i, i + 1, key_size); 155 | key = &keys[i]; 156 | } 157 | unique_keys.insert(*key); 158 | } 159 | 160 | #ifndef NDEBUG 161 | const auto end = std::chrono::high_resolution_clock::now(); 162 | const auto duration = std::chrono::duration_cast(end - start).count(); 163 | std::cout << "GENERATION DURATION: " << duration << " s." << std::endl; 164 | #endif 165 | } 166 | 167 | bool is_init_thread(const benchmark::State& state) { 168 | // Use idx = 1 because 0 starts all threads first before continuing. 169 | return state.threads == 1 || state.thread_index == 1; 170 | } 171 | 172 | void set_cpu_affinity(const uint16_t from, const uint16_t to) { 173 | const uint16_t from_cpu = from + CPU_AFFINITY_OFFSET; 174 | const uint16_t to_cpu = to + CPU_AFFINITY_OFFSET; 175 | if (from_cpu >= CPUS.size() || to_cpu > CPUS.size() || from < 0 || to < 0 || to < from) { 176 | throw std::runtime_error("Thread range invalid! " + 177 | std::to_string(from) + " -> " + std::to_string(to) + " with cpu offset " 178 | + std::to_string(CPU_AFFINITY_OFFSET)); 179 | } 180 | 181 | const auto native_thread_handle = pthread_self(); 182 | cpu_set_t cpuset; 183 | CPU_ZERO(&cpuset); 184 | for (int cpu = from_cpu; cpu < to_cpu; ++cpu) { 185 | CPU_SET(CPUS[cpu], &cpuset); 186 | } 187 | int rc = pthread_setaffinity_np(native_thread_handle, sizeof(cpu_set_t), &cpuset); 188 | if (rc != 0) { 189 | std::cerr << "Error calling pthread_setaffinity_np: " << rc << "\n"; 190 | } 191 | } 192 | 193 | void set_cpu_affinity() { 194 | cpu_set_t cpuset; 195 | CPU_ZERO(&cpuset); 196 | for (int cpu = 0; cpu < CPUS.size(); ++cpu) { 197 | CPU_SET(CPUS[cpu], &cpuset); 198 | } 199 | int rc = pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); 200 | if (rc != 0) { 201 | std::cerr << "Error calling pthread_setaffinity_np: " << rc << "\n"; 202 | } 203 | } 204 | 205 | void set_cpu_affinity(uint16_t thread_idx) { 206 | return set_cpu_affinity(thread_idx, thread_idx + 1); 207 | } 208 | 209 | void zero_block_device(const std::string& block_dev, size_t length) { 210 | int fd = open(block_dev.c_str(), O_RDWR); 211 | if (fd < 0) { 212 | throw std::runtime_error("Cannot open dax device: " + block_dev + " | " + std::strerror(errno)); 213 | } 214 | 215 | void* addr = mmap(nullptr, length, PROT_WRITE, MAP_SHARED, fd, 0); 216 | if (addr == nullptr || addr == reinterpret_cast(0xffffffffffffffff)) { 217 | throw std::runtime_error("Cannot mmap pool file: " + block_dev + " | " + std::strerror(errno)); 218 | } 219 | 220 | constexpr size_t num_write_threads = 6; 221 | constexpr size_t buffer_size = 4096; 222 | const size_t num_chunks = length / buffer_size; 223 | const size_t num_chunks_per_thread = (num_chunks / num_write_threads) + 1; 224 | 225 | std::vector zero_threads; 226 | zero_threads.reserve(num_write_threads); 227 | for (size_t thread_num = 0; thread_num < num_write_threads; ++thread_num) { 228 | char* start_addr = reinterpret_cast(addr) + (thread_num * num_chunks_per_thread); 229 | zero_threads.emplace_back([&](char* start_addr) { 230 | for (size_t i = 0; i < num_chunks_per_thread && i < num_chunks; ++i) { 231 | void* chunk_start_addr = start_addr + (i * buffer_size); 232 | memset(chunk_start_addr, 0, buffer_size); 233 | } 234 | }, start_addr); 235 | } 236 | 237 | for (std::thread& thread : zero_threads) { 238 | thread.join(); 239 | } 240 | 241 | munmap(addr, length); 242 | } 243 | 244 | } // namespace viper::kv_bm 245 | -------------------------------------------------------------------------------- /benchmark/fixtures/pmem_kv_fixture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common_fixture.hpp" 4 | #include 5 | 6 | namespace std { 7 | 8 | //template <> 9 | //struct hash { 10 | // size_t operator()(const viper::kv_bm::BMKeyFixed& key) { 11 | // return std::hash()(key.uuid[0]); 12 | // } 13 | //}; 14 | } 15 | 16 | namespace viper { 17 | namespace kv_bm { 18 | 19 | template 20 | class PmemKVFixture : public BaseFixture { 21 | public: 22 | void InitMap(const uint64_t num_prefill_inserts = 0, const bool re_init = true) override; 23 | void DeInitMap() override; 24 | 25 | uint64_t insert(uint64_t start_idx, uint64_t end_idx) final; 26 | 27 | uint64_t setup_and_insert(uint64_t start_idx, uint64_t end_idx) final; 28 | uint64_t setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) final; 29 | uint64_t setup_and_delete(uint64_t start_idx, uint64_t end_idx, uint64_t num_deletes) final; 30 | uint64_t setup_and_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates) final; 31 | 32 | uint64_t run_ycsb(uint64_t start_idx, uint64_t end_idx, 33 | const std::vector& data, hdr_histogram* hdr) final; 34 | 35 | protected: 36 | std::unique_ptr pmem_db_; 37 | std::string pool_file_; 38 | bool db_initialized_ = false; 39 | }; 40 | 41 | template 42 | void PmemKVFixture::InitMap(uint64_t num_prefill_inserts, const bool re_init) { 43 | if (db_initialized_ && !re_init) { 44 | return; 45 | } 46 | 47 | int sds_write_value = 0; 48 | pmemobj_ctl_set(NULL, "sds.at_create", &sds_write_value); 49 | 50 | pool_file_ = random_file(DB_PMEM_DIR); 51 | pmem_db_ = std::make_unique(); 52 | 53 | const size_t expected_pool_file_size = 90 * ONE_GB; 54 | pmem::kv::config config{}; 55 | config.put_string("path", pool_file_); 56 | config.put_uint64("size", expected_pool_file_size); 57 | config.put_uint64("force_create", 1); 58 | pmem::kv::status s = pmem_db_->open("cmap", std::move(config)); 59 | if (s != pmem::kv::status::OK) { 60 | throw std::runtime_error("Could not open PmemKV!"); 61 | } 62 | 63 | prefill(num_prefill_inserts); 64 | db_initialized_ = true; 65 | } 66 | 67 | template 68 | void PmemKVFixture::DeInitMap() { 69 | BaseFixture::DeInitMap(); 70 | pmem_db_->close(); 71 | pmem_db_ = nullptr; 72 | db_initialized_ = false; 73 | pmempool_rm(pool_file_.c_str(), PMEMPOOL_RM_FORCE | PMEMPOOL_RM_POOLSET_LOCAL); 74 | std::filesystem::remove(pool_file_); 75 | } 76 | 77 | template 78 | uint64_t PmemKVFixture::insert(uint64_t start_idx, uint64_t end_idx) { 79 | uint64_t insert_counter = 0; 80 | for (uint64_t key = start_idx; key < end_idx; ++key) { 81 | const KeyT k{key}; 82 | const ValueT v{key}; 83 | const pmem::kv::string_view db_key{(char*) &k.data, sizeof(KeyT)}; 84 | const pmem::kv::string_view value_str{(char*) &v.data, sizeof(ValueT)}; 85 | insert_counter += pmem_db_->put(db_key, value_str) == pmem::kv::status::OK; 86 | } 87 | return insert_counter; 88 | } 89 | 90 | template <> 91 | uint64_t PmemKVFixture::insert(uint64_t start_idx, uint64_t end_idx) { 92 | uint64_t insert_counter = 0; 93 | const std::vector& keys = std::get<0>(var_size_kvs_); 94 | const std::vector& values = std::get<1>(var_size_kvs_); 95 | for (uint64_t key = start_idx; key < end_idx; ++key) { 96 | const std::string& db_key = keys[key]; 97 | const std::string& value = values[key]; 98 | insert_counter += pmem_db_->put(db_key, value) == pmem::kv::status::OK; 99 | } 100 | return insert_counter; 101 | } 102 | 103 | template 104 | uint64_t PmemKVFixture::setup_and_insert(uint64_t start_idx, uint64_t end_idx) { 105 | return insert(start_idx, end_idx); 106 | } 107 | 108 | template 109 | uint64_t PmemKVFixture::setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) { 110 | std::random_device rnd{}; 111 | auto rnd_engine = std::default_random_engine(rnd()); 112 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 113 | 114 | uint64_t found_counter = 0; 115 | for (uint64_t i = 0; i < num_finds; ++i) { 116 | const uint64_t key = distrib(rnd_engine); 117 | const KeyT k{key}; 118 | std::string value; 119 | const pmem::kv::string_view db_key{(char*) &k.data, sizeof(KeyT)}; 120 | 121 | const bool found = pmem_db_->get(db_key, &value) == pmem::kv::status::OK; 122 | if (found) { 123 | found_counter += ValueT{}.from_str(value).data[0] == key; 124 | } 125 | } 126 | return found_counter; 127 | } 128 | 129 | template <> 130 | uint64_t PmemKVFixture::setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) { 131 | std::random_device rnd{}; 132 | auto rnd_engine = std::default_random_engine(rnd()); 133 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 134 | 135 | const std::vector& keys = std::get<0>(var_size_kvs_); 136 | const std::vector& values = std::get<1>(var_size_kvs_); 137 | 138 | uint64_t found_counter = 0; 139 | for (uint64_t i = 0; i < num_finds; ++i) { 140 | const uint64_t key = distrib(rnd_engine); 141 | const std::string& db_key = keys[key]; 142 | const std::string& value = values[key]; 143 | std::string result{}; 144 | 145 | const bool found = pmem_db_->get(db_key, &result) == pmem::kv::status::OK; 146 | found_counter += found && result == value; 147 | } 148 | return found_counter; 149 | } 150 | 151 | template 152 | uint64_t PmemKVFixture::setup_and_delete(uint64_t start_idx, uint64_t end_idx, uint64_t num_deletes) { 153 | std::random_device rnd{}; 154 | auto rnd_engine = std::default_random_engine(rnd()); 155 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 156 | 157 | uint64_t delete_counter = 0; 158 | for (uint64_t i = 0; i < num_deletes; ++i) { 159 | const uint64_t key = distrib(rnd_engine); 160 | const KeyT k{key}; 161 | const pmem::kv::string_view db_key{(char*) &k.data, sizeof(KeyT)}; 162 | delete_counter += pmem_db_->remove(db_key) == pmem::kv::status::OK; 163 | } 164 | return delete_counter; 165 | } 166 | 167 | template 168 | uint64_t PmemKVFixture::setup_and_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates) { 169 | std::random_device rnd{}; 170 | auto rnd_engine = std::default_random_engine(rnd()); 171 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 172 | 173 | uint64_t update_counter = 0; 174 | for (uint64_t i = 0; i < num_updates; ++i) { 175 | std::string value; 176 | const uint64_t key = distrib(rnd_engine); 177 | const KeyT k{key}; 178 | const pmem::kv::string_view db_key{(char*) &k.data, sizeof(KeyT)}; 179 | 180 | const bool found = pmem_db_->get(db_key, &value) == pmem::kv::status::OK; 181 | if (found) { 182 | ValueT new_val{}; 183 | new_val.from_str(value); 184 | new_val.update_value(); 185 | const pmem::kv::string_view new_val_str{(char*) &new_val.data, sizeof(ValueT)}; 186 | update_counter += pmem_db_->put(db_key, new_val_str) == pmem::kv::status::OK; 187 | } 188 | } 189 | return update_counter; 190 | } 191 | 192 | template <> 193 | uint64_t PmemKVFixture::setup_and_update(uint64_t, uint64_t, uint64_t) { 194 | return 0; 195 | } 196 | 197 | template <> 198 | uint64_t PmemKVFixture::setup_and_delete(uint64_t, uint64_t, uint64_t) { 199 | return 0; 200 | } 201 | 202 | template 203 | uint64_t PmemKVFixture::run_ycsb(uint64_t, uint64_t, const std::vector&, hdr_histogram*) { 204 | throw std::runtime_error{"YCSB not implemented for non-ycsb key/value types."}; 205 | } 206 | 207 | template <> 208 | uint64_t PmemKVFixture::run_ycsb( 209 | uint64_t start_idx, uint64_t end_idx, const std::vector& data, hdr_histogram* hdr) { 210 | uint64_t op_count = 0; 211 | 212 | std::chrono::high_resolution_clock::time_point start; 213 | std::string value; 214 | for (int op_num = start_idx; op_num < end_idx; ++op_num) { 215 | const ycsb::Record& record = data[op_num]; 216 | 217 | if (hdr != nullptr) { 218 | start = std::chrono::high_resolution_clock::now(); 219 | } 220 | 221 | switch (record.op) { 222 | case ycsb::Record::Op::INSERT: { 223 | const pmem::kv::string_view db_key{(char*) &record.key.data, sizeof(KeyType8)}; 224 | const pmem::kv::string_view value_str{(char*) &record.value.data, sizeof(ValueType200)}; 225 | op_count += pmem_db_->put(db_key, value_str) == pmem::kv::status::OK; 226 | break; 227 | } 228 | case ycsb::Record::Op::GET: { 229 | const pmem::kv::string_view db_key{(char*) &record.key.data, sizeof(KeyType8)}; 230 | 231 | const bool found = pmem_db_->get(db_key, &value) == pmem::kv::status::OK; 232 | if (found) { 233 | op_count += ValueType200 {}.from_str(value).data[0] != 0; 234 | } 235 | break; 236 | } 237 | case ycsb::Record::Op::UPDATE: { 238 | std::string old_value; 239 | const pmem::kv::string_view db_key{(char*) &record.key.data, sizeof(KeyType8)}; 240 | 241 | const bool found = pmem_db_->get(db_key, &old_value) == pmem::kv::status::OK; 242 | if (found) { 243 | ValueType200 new_val{}; 244 | new_val.from_str(old_value); 245 | new_val.update_value(); 246 | const pmem::kv::string_view new_val_str{(char*) &new_val.data, sizeof(ValueType200)}; 247 | op_count += pmem_db_->put(db_key, new_val_str) == pmem::kv::status::OK; 248 | } 249 | break; 250 | } 251 | default: { 252 | throw std::runtime_error("Unknown operation: " + std::to_string(record.op)); 253 | } 254 | } 255 | 256 | if (hdr == nullptr) { 257 | continue; 258 | } 259 | 260 | const auto end = std::chrono::high_resolution_clock::now(); 261 | const auto duration = std::chrono::duration_cast(end - start); 262 | hdr_record_value(hdr, duration.count()); 263 | } 264 | 265 | return op_count; 266 | } 267 | 268 | } // namespace kv_bm 269 | } // namespace viper 270 | -------------------------------------------------------------------------------- /benchmark/fixtures/viper_fixture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../benchmark.hpp" 4 | #include "common_fixture.hpp" 5 | #include "viper/viper.hpp" 6 | 7 | 8 | namespace viper { 9 | namespace kv_bm { 10 | 11 | template 12 | class ViperFixture : public BaseFixture { 13 | public: 14 | typedef KeyT KeyType; 15 | using ViperT = Viper; 16 | 17 | void InitMap(const uint64_t num_prefill_inserts = 0, const bool re_init = true) override; 18 | void InitMap(const uint64_t num_prefill_inserts, ViperConfig v_config); 19 | 20 | void DeInitMap() override; 21 | 22 | uint64_t insert(uint64_t start_idx, uint64_t end_idx) final; 23 | 24 | uint64_t setup_and_insert(uint64_t start_idx, uint64_t end_idx) final; 25 | uint64_t setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) final; 26 | uint64_t setup_and_delete(uint64_t start_idx, uint64_t end_idx, uint64_t num_deletes) final; 27 | uint64_t setup_and_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates) final; 28 | 29 | uint64_t setup_and_get_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates); 30 | 31 | uint64_t run_ycsb(uint64_t start_idx, uint64_t end_idx, 32 | const std::vector& data, hdr_histogram* hdr) final; 33 | 34 | ViperT* getViper() { 35 | return viper_.get(); 36 | } 37 | 38 | protected: 39 | std::unique_ptr viper_; 40 | bool viper_initialized_ = false; 41 | std::string pool_file_; 42 | }; 43 | 44 | template 45 | void ViperFixture::InitMap(uint64_t num_prefill_inserts, const bool re_init) { 46 | if (viper_initialized_ && !re_init) { 47 | return; 48 | } 49 | 50 | return InitMap(num_prefill_inserts, ViperConfig{}); 51 | } 52 | 53 | template 54 | void ViperFixture::InitMap(uint64_t num_prefill_inserts, ViperConfig v_config) { 55 | #ifdef CCEH_PERSISTENT 56 | PMemAllocator::get().initialize(); 57 | #endif 58 | 59 | pool_file_ = VIPER_POOL_FILE; 60 | // pool_file_ = random_file(DB_PMEM_DIR); 61 | // pool_file_ = DB_PMEM_DIR + std::string("/viper"); 62 | 63 | // viper_ = ViperT::open(pool_file_, v_config); 64 | viper_ = ViperT::create(pool_file_, BM_POOL_SIZE, v_config); 65 | this->prefill(num_prefill_inserts); 66 | viper_initialized_ = true; 67 | } 68 | 69 | template 70 | void ViperFixture::DeInitMap() { 71 | BaseFixture::DeInitMap(); 72 | viper_ = nullptr; 73 | viper_initialized_ = false; 74 | if (pool_file_.find("/dev/dax") == std::string::npos) { 75 | std::filesystem::remove_all(pool_file_); 76 | } 77 | } 78 | 79 | template 80 | uint64_t ViperFixture::insert(uint64_t start_idx, uint64_t end_idx) { 81 | uint64_t insert_counter = 0; 82 | auto v_client = viper_->get_client(); 83 | for (uint64_t key = start_idx; key < end_idx; ++key) { 84 | const KeyT db_key{key}; 85 | const ValueT value{key}; 86 | insert_counter += v_client.put(db_key, value); 87 | } 88 | return insert_counter; 89 | } 90 | 91 | template <> 92 | uint64_t ViperFixture::insert(uint64_t start_idx, uint64_t end_idx) { 93 | uint64_t insert_counter = 0; 94 | auto v_client = viper_->get_client(); 95 | const std::vector& keys = std::get<0>(var_size_kvs_); 96 | const std::vector& values = std::get<1>(var_size_kvs_); 97 | for (uint64_t key = start_idx; key < end_idx; ++key) { 98 | const std::string& db_key = keys[key]; 99 | const std::string& value = values[key]; 100 | insert_counter += v_client.put(db_key, value); 101 | } 102 | return insert_counter; 103 | } 104 | 105 | template 106 | uint64_t ViperFixture::setup_and_insert(uint64_t start_idx, uint64_t end_idx) { 107 | return insert(start_idx, end_idx); 108 | } 109 | 110 | template 111 | uint64_t ViperFixture::setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) { 112 | std::random_device rnd{}; 113 | auto rnd_engine = std::default_random_engine(rnd()); 114 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 115 | 116 | const auto v_client = viper_->get_read_only_client(); 117 | uint64_t found_counter = 0; 118 | ValueT value; 119 | for (uint64_t i = 0; i < num_finds; ++i) { 120 | const uint64_t key = distrib(rnd_engine); 121 | const KeyT db_key{key}; 122 | const bool found = v_client.get(db_key, &value); 123 | found_counter += found && (value == ValueT{key}); 124 | } 125 | return found_counter; 126 | } 127 | 128 | template <> 129 | uint64_t ViperFixture::setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) { 130 | std::random_device rnd{}; 131 | auto rnd_engine = std::default_random_engine(rnd()); 132 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 133 | 134 | const std::vector& keys = std::get<0>(var_size_kvs_); 135 | const std::vector& values = std::get<1>(var_size_kvs_); 136 | 137 | auto v_client = viper_->get_read_only_client(); 138 | uint64_t found_counter = 0; 139 | std::string result; 140 | for (uint64_t i = 0; i < num_finds; ++i) { 141 | const uint64_t key = distrib(rnd_engine); 142 | const std::string& db_key = keys[key]; 143 | const std::string& value = values[key]; 144 | const bool found = v_client.get(db_key, &result); 145 | found_counter += found && (result == value); 146 | } 147 | return found_counter; 148 | } 149 | 150 | template 151 | uint64_t ViperFixture::setup_and_delete(uint64_t start_idx, uint64_t end_idx, uint64_t num_deletes) { 152 | std::random_device rnd{}; 153 | auto rnd_engine = std::default_random_engine(rnd()); 154 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 155 | 156 | auto v_client = viper_->get_client(); 157 | uint64_t delete_counter = 0; 158 | for (uint64_t i = 0; i < num_deletes; ++i) { 159 | const uint64_t key = distrib(rnd_engine); 160 | const KeyT db_key{key}; 161 | delete_counter += v_client.remove(db_key); 162 | } 163 | return delete_counter; 164 | } 165 | 166 | template <> 167 | uint64_t ViperFixture::setup_and_delete(uint64_t start_idx, uint64_t end_idx, uint64_t num_deletes) { 168 | std::random_device rnd{}; 169 | auto rnd_engine = std::default_random_engine(rnd()); 170 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 171 | const std::vector& keys = std::get<0>(var_size_kvs_); 172 | 173 | auto v_client = viper_->get_client(); 174 | uint64_t delete_counter = 0; 175 | for (uint64_t i = 0; i < num_deletes; ++i) { 176 | const uint64_t key = distrib(rnd_engine); 177 | const std::string& db_key = keys[key]; 178 | delete_counter += v_client.remove(db_key); 179 | } 180 | return delete_counter; 181 | } 182 | 183 | template 184 | uint64_t ViperFixture::setup_and_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates) { 185 | std::random_device rnd{}; 186 | auto rnd_engine = std::default_random_engine(rnd()); 187 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 188 | 189 | auto v_client = viper_->get_client(); 190 | uint64_t update_counter = 0; 191 | 192 | auto update_fn = [](ValueT* value) { 193 | value->update_value(); 194 | internal::pmem_persist(value, sizeof(uint64_t)); 195 | }; 196 | 197 | for (uint64_t i = 0; i < num_updates; ++i) { 198 | const uint64_t key = distrib(rnd_engine); 199 | const KeyT db_key{key}; 200 | update_counter += v_client.update(db_key, update_fn); 201 | } 202 | return update_counter; 203 | } 204 | 205 | template <> 206 | uint64_t ViperFixture::setup_and_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates) { 207 | throw std::runtime_error("Not supported"); 208 | } 209 | 210 | template 211 | uint64_t ViperFixture::setup_and_get_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates) { 212 | std::random_device rnd{}; 213 | auto rnd_engine = std::default_random_engine(rnd()); 214 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 215 | 216 | auto v_client = viper_->get_client(); 217 | 218 | ValueT new_v{}; 219 | for (uint64_t i = 0; i < num_updates; ++i) { 220 | const uint64_t key = distrib(rnd_engine); 221 | const KeyT db_key{key}; 222 | v_client.get(db_key, &new_v); 223 | new_v.update_value(); 224 | v_client.put(db_key, new_v); 225 | } 226 | 227 | return num_updates; 228 | } 229 | 230 | template <> 231 | uint64_t ViperFixture::setup_and_get_update(uint64_t, uint64_t, uint64_t) { 232 | throw std::runtime_error("Not supported"); 233 | } 234 | 235 | template 236 | uint64_t ViperFixture::run_ycsb(uint64_t, uint64_t, const std::vector&, hdr_histogram*) { 237 | throw std::runtime_error{"YCSB not implemented for non-ycsb key/value types."}; 238 | } 239 | 240 | template <> 241 | uint64_t ViperFixture::run_ycsb( 242 | uint64_t start_idx, uint64_t end_idx, const std::vector& data, hdr_histogram* hdr) { 243 | uint64_t op_count = 0; 244 | auto v_client = viper_->get_client(); 245 | ValueType200 value; 246 | const ValueType200 null_value{0ul}; 247 | 248 | std::chrono::high_resolution_clock::time_point start; 249 | for (int op_num = start_idx; op_num < end_idx; ++op_num) { 250 | const ycsb::Record& record = data[op_num]; 251 | 252 | if (hdr != nullptr) { 253 | start = std::chrono::high_resolution_clock::now(); 254 | } 255 | 256 | switch (record.op) { 257 | case ycsb::Record::Op::INSERT: { 258 | v_client.put(record.key, record.value); 259 | op_count++; 260 | break; 261 | } 262 | case ycsb::Record::Op::GET: { 263 | const bool found = v_client.get(record.key, &value); 264 | op_count += found && (value != null_value); 265 | break; 266 | } 267 | case ycsb::Record::Op::UPDATE: { 268 | auto update_fn = [&](ValueType200* value) { 269 | value->data[0] = record.value.data[0]; 270 | internal::pmem_persist(value->data.data(), sizeof(uint64_t)); 271 | }; 272 | op_count += v_client.update(record.key, update_fn); 273 | break; 274 | } 275 | default: { 276 | throw std::runtime_error("Unknown operation: " + std::to_string(record.op)); 277 | } 278 | } 279 | 280 | 281 | if (hdr == nullptr) { 282 | continue; 283 | } 284 | 285 | const auto end = std::chrono::high_resolution_clock::now(); 286 | const auto duration = std::chrono::duration_cast(end - start); 287 | hdr_record_value(hdr, duration.count()); 288 | } 289 | 290 | return op_count; 291 | } 292 | 293 | } // namespace kv_bm 294 | } // namespace viper 295 | -------------------------------------------------------------------------------- /benchmark/fixtures/cceh_fixture.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "viper/cceh.hpp" 4 | #include "common_fixture.hpp" 5 | #include "../benchmark.hpp" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace viper::kv_bm { 12 | 13 | template 14 | class CcehFixture : public BaseFixture { 15 | using Entry = std::pair; 16 | using EntryVector = pmem::obj::vector>; 17 | 18 | struct CcehPool { 19 | pmem::obj::persistent_ptr ptrs; 20 | }; 21 | 22 | public: 23 | void InitMap(const uint64_t num_prefill_inserts, const bool re_init) final; 24 | void DeInitMap() final; 25 | uint64_t setup_and_insert(uint64_t start_idx, uint64_t end_idx) final; 26 | uint64_t setup_and_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates); 27 | uint64_t setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds); 28 | uint64_t setup_and_delete(uint64_t start_idx, uint64_t end_idx, uint64_t num_deletes); 29 | uint64_t run_ycsb(uint64_t start_idx, uint64_t end_idx, const std::vector& data, 30 | hdr_histogram* hdr) final; 31 | uint64_t insert(uint64_t start_idx, uint64_t end_idx) final; 32 | void prefill_ycsb(const std::vector& data) override; 33 | 34 | protected: 35 | std::unique_ptr> dram_map_; 36 | pmem::obj::pool pmem_pool_; 37 | EntryVector* ptrs_; 38 | std::atomic pool_vector_pos_; 39 | std::string pmem_pool_name_; 40 | bool map_initialized_ = false; 41 | 42 | bool insert_internal(const KeyT& key, const ValueT& value); 43 | }; 44 | 45 | template 46 | void CcehFixture::InitMap(const uint64_t num_prefill_inserts, const bool re_init) { 47 | if (map_initialized_ && !re_init) { 48 | return; 49 | } 50 | 51 | pmem_pool_name_ = random_file(DB_PMEM_DIR); 52 | int sds_write_value = 0; 53 | pmemobj_ctl_set(NULL, "sds.at_create", &sds_write_value); 54 | pmem_pool_ = pmem::obj::pool::create(pmem_pool_name_, "", 80ul * ONE_GB, S_IRWXU); 55 | if (pmem_pool_.handle() == nullptr) { 56 | throw std::runtime_error("Could not create pool"); 57 | } 58 | pmem::obj::transaction::run(pmem_pool_, [&] { 59 | pmem_pool_.root()->ptrs = pmem::obj::make_persistent(); 60 | ptrs_ = pmem_pool_.root()->ptrs.get(); 61 | ptrs_->resize(num_prefill_inserts * 2); 62 | }); 63 | 64 | pool_vector_pos_ = 0; 65 | dram_map_ = std::make_unique>(1000000); 66 | prefill(num_prefill_inserts); 67 | map_initialized_ = true; 68 | } 69 | 70 | template 71 | void CcehFixture::DeInitMap() { 72 | pmem_pool_.close(); 73 | pmempool_rm(pmem_pool_name_.c_str(), 0); 74 | dram_map_ = nullptr; 75 | map_initialized_ = false; 76 | } 77 | 78 | template 79 | inline bool CcehFixture::insert_internal(const KeyT& key, const ValueT& value) { 80 | block_size_t ptrs_pos; 81 | pmem::obj::persistent_ptr offset_ptr; 82 | pmem::obj::transaction::run(pmem_pool_, [&] { 83 | offset_ptr = pmem::obj::make_persistent(key, value); 84 | ptrs_pos = pool_vector_pos_.fetch_add(1); 85 | (*ptrs_)[ptrs_pos] = offset_ptr; 86 | }); 87 | KeyValueOffset offset{ptrs_pos, 0, 0}; 88 | KeyValueOffset old_offset = dram_map_->Insert(key, offset); 89 | return old_offset.is_tombstone(); 90 | } 91 | 92 | template 93 | uint64_t CcehFixture::insert(uint64_t start_idx, uint64_t end_idx) { 94 | uint64_t insert_counter = 0; 95 | for (uint64_t pos = start_idx; pos < end_idx; ++pos) { 96 | const KeyT db_key{pos}; 97 | const ValueT value{pos}; 98 | insert_counter += insert_internal(db_key, value); 99 | } 100 | return insert_counter; 101 | } 102 | 103 | template <> 104 | uint64_t CcehFixture::insert(uint64_t start_idx, uint64_t end_idx) { 105 | uint64_t insert_counter = 0; 106 | const std::vector& keys = std::get<0>(var_size_kvs_); 107 | const std::vector& values = std::get<1>(var_size_kvs_); 108 | for (uint64_t pos = start_idx; pos < end_idx; ++pos) { 109 | const std::string& db_key = keys[pos]; 110 | const std::string& value = values[pos]; 111 | insert_counter += insert_internal(db_key, value); 112 | } 113 | return insert_counter; 114 | } 115 | 116 | template 117 | uint64_t CcehFixture::setup_and_insert(uint64_t start_idx, uint64_t end_idx) { 118 | return insert(start_idx, end_idx); 119 | } 120 | 121 | template 122 | uint64_t CcehFixture::setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) { 123 | std::random_device rnd{}; 124 | auto rnd_engine = std::default_random_engine(rnd()); 125 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 126 | 127 | auto key_check_fn = [this](const KeyT& key, IndexV offset) { 128 | block_size_t entry_ptr_pos = offset.block_number; 129 | const pmem::obj::persistent_ptr& entry_ptr = (*ptrs_)[entry_ptr_pos]; 130 | return key == entry_ptr->first; 131 | }; 132 | 133 | uint64_t found_counter = 0; 134 | for (uint64_t i = 0; i < num_finds; ++i) { 135 | const uint64_t key = distrib(rnd_engine); 136 | const KeyValueOffset offset = dram_map_->Get(key, key_check_fn); 137 | if (!offset.is_tombstone()) { 138 | block_size_t entry_ptr_pos = offset.block_number; 139 | pmem::obj::persistent_ptr entry_ptr = (*ptrs_)[entry_ptr_pos]; 140 | ValueT found_val = entry_ptr->second; 141 | found_counter += (found_val.data[0] == key); 142 | } 143 | } 144 | return found_counter; 145 | } 146 | 147 | template <> 148 | uint64_t CcehFixture::setup_and_find(uint64_t start_idx, uint64_t end_idx, uint64_t num_finds) { 149 | std::random_device rnd{}; 150 | auto rnd_engine = std::default_random_engine(rnd()); 151 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 152 | 153 | auto key_check_fn = [this](const std::string& key, IndexV offset) { 154 | block_size_t entry_ptr_pos = offset.block_number; 155 | pmem::obj::persistent_ptr entry_ptr = (*ptrs_)[entry_ptr_pos]; 156 | return key == entry_ptr->first; 157 | }; 158 | 159 | const std::vector& keys = std::get<0>(var_size_kvs_); 160 | const std::vector& values = std::get<1>(var_size_kvs_); 161 | 162 | uint64_t found_counter = 0; 163 | for (uint64_t i = 0; i < num_finds; ++i) { 164 | const uint64_t key = distrib(rnd_engine); 165 | const std::string& db_key = keys[key]; 166 | const std::string& value = values[key]; 167 | const KeyValueOffset offset = dram_map_->Get(db_key, key_check_fn); 168 | if (!offset.is_tombstone()) { 169 | block_size_t entry_ptr_pos = offset.block_number; 170 | pmem::obj::persistent_ptr entry_ptr = (*ptrs_)[entry_ptr_pos]; 171 | found_counter += (entry_ptr->second == value); 172 | } 173 | } 174 | return found_counter; 175 | } 176 | 177 | template 178 | uint64_t CcehFixture::setup_and_update(uint64_t start_idx, uint64_t end_idx, uint64_t num_updates) { 179 | std::random_device rnd{}; 180 | auto rnd_engine = std::default_random_engine(rnd()); 181 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 182 | 183 | auto key_check_fn = [this](const KeyT& key, IndexV offset) { 184 | block_size_t entry_ptr_pos = offset.block_number; 185 | pmem::obj::persistent_ptr entry_ptr = (*ptrs_)[entry_ptr_pos]; 186 | return key == entry_ptr->first; 187 | }; 188 | 189 | uint64_t update_counter = 0; 190 | for (uint64_t i = 0; i < num_updates; ++i) { 191 | const uint64_t key = distrib(rnd_engine); 192 | const KeyT db_key{key}; 193 | const KeyValueOffset offset = dram_map_->Get(db_key, key_check_fn); 194 | if (!offset.is_tombstone()) { 195 | block_size_t entry_ptr_pos = offset.block_number; 196 | pmem::obj::persistent_ptr entry_ptr = (*ptrs_)[entry_ptr_pos]; 197 | ValueT& value = entry_ptr->second; 198 | value.update_value(); 199 | pmem_persist(&value, sizeof(uint64_t)); 200 | update_counter++; 201 | } 202 | } 203 | return update_counter; 204 | } 205 | 206 | template <> 207 | uint64_t CcehFixture::setup_and_update(uint64_t, uint64_t, uint64_t) { return 0; } 208 | 209 | template 210 | uint64_t CcehFixture::setup_and_delete(uint64_t start_idx, uint64_t end_idx, uint64_t num_deletes) { 211 | std::random_device rnd{}; 212 | auto rnd_engine = std::default_random_engine(rnd()); 213 | std::uniform_int_distribution<> distrib(start_idx, end_idx); 214 | 215 | auto key_check_fn = [this](const KeyT& key, IndexV offset) { 216 | block_size_t entry_ptr_pos = offset.block_number; 217 | const pmem::obj::persistent_ptr& entry_ptr = (*ptrs_)[entry_ptr_pos]; 218 | return !!entry_ptr && key == entry_ptr->first; 219 | }; 220 | 221 | uint64_t delete_counter = 0; 222 | for (uint64_t i = 0; i < num_deletes; ++i) { 223 | const uint64_t key = distrib(rnd_engine); 224 | const KeyT db_key{key}; 225 | const KeyValueOffset offset = dram_map_->Get(db_key, key_check_fn); 226 | if (!offset.is_tombstone()) { 227 | block_size_t entry_ptr_pos = offset.block_number; 228 | pmem::obj::persistent_ptr entry_ptr = (*ptrs_)[entry_ptr_pos]; 229 | pmem::obj::transaction::run(pmem_pool_, [&] { 230 | pmem::obj::delete_persistent(entry_ptr); 231 | }); 232 | (*ptrs_)[entry_ptr_pos] = pmem::obj::persistent_ptr(); 233 | dram_map_->Insert(key, IndexV::NONE(), key_check_fn); 234 | delete_counter++; 235 | } 236 | } 237 | return delete_counter; 238 | } 239 | 240 | template <> 241 | uint64_t CcehFixture::setup_and_delete(uint64_t, uint64_t, uint64_t) { return 0; } 242 | 243 | template 244 | uint64_t CcehFixture::run_ycsb(uint64_t, uint64_t, const std::vector&, hdr_histogram*) { 245 | throw std::runtime_error{"YCSB not implemented for non-ycsb key/value types."}; 246 | } 247 | 248 | template <> 249 | uint64_t CcehFixture::run_ycsb(uint64_t start_idx, 250 | uint64_t end_idx, const std::vector& data, hdr_histogram* hdr) { 251 | 252 | uint64_t op_count = 0; 253 | for (int op_num = start_idx; op_num < end_idx; ++op_num) { 254 | const ycsb::Record& record = data[op_num]; 255 | 256 | const auto start = std::chrono::high_resolution_clock::now(); 257 | 258 | switch (record.op) { 259 | case ycsb::Record::Op::INSERT: { 260 | insert_internal(record.key, record.value); 261 | op_count++; 262 | break; 263 | } 264 | case ycsb::Record::Op::GET: { 265 | const KeyValueOffset offset = dram_map_->Get(record.key); 266 | if (!offset.is_tombstone()) { 267 | block_size_t entry_ptr_pos = offset.block_number; 268 | pmem::obj::persistent_ptr entry_ptr = (*ptrs_)[entry_ptr_pos]; 269 | op_count += (entry_ptr->second == record.value); 270 | } 271 | break; 272 | } 273 | case ycsb::Record::Op::UPDATE: { 274 | insert_internal(record.key, record.value); 275 | op_count++; 276 | break; 277 | } 278 | default: { 279 | throw std::runtime_error("Unknown operation: " + std::to_string(record.op)); 280 | } 281 | } 282 | 283 | if (hdr == nullptr) { 284 | continue; 285 | } 286 | 287 | const auto end = std::chrono::high_resolution_clock::now(); 288 | const auto duration = std::chrono::duration_cast(end - start); 289 | hdr_record_value(hdr, duration.count()); 290 | } 291 | 292 | return op_count; 293 | } 294 | 295 | template 296 | void CcehFixture::prefill_ycsb(const std::vector& data) { 297 | ptrs_->resize(data.size() * 2); 298 | BaseFixture::prefill_ycsb(data); 299 | } 300 | 301 | } // namespace 302 | --------------------------------------------------------------------------------