From 8fe1b8d7fc9fdf9b80aa2a1f12c4ff9bec103fa4 Mon Sep 17 00:00:00 2001 From: jbajic <jure.bajic@memgraph.com> Date: Thu, 24 Nov 2022 17:44:41 +0100 Subject: [PATCH] Add std::map and skiplist benchamrk --- tests/benchmark/CMakeLists.txt | 11 +- tests/benchmark/data_structures.cpp | 169 +++++++++++++++++++++++++++ tests/benchmark/skip_list_common.hpp | 26 ++++- 3 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 tests/benchmark/data_structures.cpp diff --git a/tests/benchmark/CMakeLists.txt b/tests/benchmark/CMakeLists.txt index 31f0eebc0..46905a4d8 100644 --- a/tests/benchmark/CMakeLists.txt +++ b/tests/benchmark/CMakeLists.txt @@ -9,11 +9,13 @@ function(add_benchmark test_cpp) get_filename_component(exec_name ${test_cpp} NAME_WE) set(target_name ${test_prefix}${exec_name}) add_executable(${target_name} ${test_cpp} ${ARGN}) + # OUTPUT_NAME sets the real name of a target when it is built and can be # used to help create two targets of the same name even though CMake # requires unique logical target names set_target_properties(${target_name} PROPERTIES OUTPUT_NAME ${exec_name}) target_link_libraries(${target_name} benchmark gflags) + # register test add_test(${target_name} ${exec_name}) add_dependencies(memgraph__benchmark ${target_name}) @@ -37,9 +39,9 @@ target_link_libraries(${test_prefix}profile mg-query) add_benchmark(query/stripped.cpp) target_link_libraries(${test_prefix}stripped mg-query) -if (MG_ENTERPRISE) -add_benchmark(rpc.cpp) -target_link_libraries(${test_prefix}rpc mg-rpc) +if(MG_ENTERPRISE) + add_benchmark(rpc.cpp) + target_link_libraries(${test_prefix}rpc mg-rpc) endif() add_benchmark(skip_list_random.cpp) @@ -65,3 +67,6 @@ target_link_libraries(${test_prefix}storage_v2_property_store mg-storage-v2) add_benchmark(future.cpp) target_link_libraries(${test_prefix}future mg-io) + +add_benchmark(data_structures.cpp) +target_link_libraries(${test_prefix}data_structures mg-utils) diff --git a/tests/benchmark/data_structures.cpp b/tests/benchmark/data_structures.cpp new file mode 100644 index 000000000..f130d6faf --- /dev/null +++ b/tests/benchmark/data_structures.cpp @@ -0,0 +1,169 @@ +// Copyright 2022 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#include <gflags/gflags.h> +#include <atomic> +#include <concepts> +#include <cstddef> +#include <cstdint> +#include <exception> +#include <map> +#include <stdexcept> +#include <type_traits> + +#include "btree_map.hpp" +#include "skip_list_common.hpp" +#include "utils/skip_list.hpp" + +DEFINE_int32(max_element, 20000, "Maximum element in the intial list"); +DEFINE_int32(max_range, 2000000, "Maximum range used for the test"); +DEFINE_string(ds, "skip_list", "Which DS to test"); + +template <class ContainerType> +concept BasicIndexStructure = requires(ContainerType a, const ContainerType b) { + { a.contains() } -> std::same_as<bool>; + { a.find() } -> std::same_as<typename ContainerType::iterator>; + { a.insert() } -> std::same_as<std::pair<typename ContainerType::iterator, bool>>; + { a.remove() } -> std::same_as<bool>; +}; + +template <typename T> +class BppStructure final { + using value_type = typename tlx::btree_map<T, T>::value_type; + using iterator = typename tlx::btree_map<T, T>::iterator; + + public: + bool contains(const T key) const { return data.exists(key); } + + auto find(const T key) const { return data.find(key); } + + std::pair<iterator, bool> insert(T val) { return data.insert({val, val}); } + + bool remove(const T key) { return data.erase(key); } + + size_t size() const { return data.size(); }; + + iterator end() { return data.end(); } + + private: + tlx::btree_map<T, T> data; +}; + +template <typename T> +class MapStructure final { + using value_type = typename std::map<T, T>::value_type; + using iterator = typename std::map<T, T>::iterator; + + public: + bool contains(const T key) const { return data.contains(key); } + + auto find(const T key) const { return data.find(key); } + + std::pair<iterator, bool> insert(T val) { return data.insert({val, val}); } + + bool remove(const T key) { return data.erase(key); } + + size_t size() const { return data.size(); }; + + iterator end() { return data.end(); } + + private: + std::map<T, T> data; +}; + +template <typename DataStructure> +void RunDSTest(DataStructure &ds) { + RunTest([&ds](const std::atomic<bool> &run, auto &stats) { + std::mt19937 generator(std::random_device{}()); + std::uniform_int_distribution<uint64_t> distribution(0, 3); + std::mt19937 i_generator(std::random_device{}()); + std::uniform_int_distribution<uint64_t> i_distribution(0, FLAGS_max_range); + + while (run.load(std::memory_order_relaxed)) { + auto value = distribution(generator); + + auto item = i_distribution(i_generator); + switch (value) { + case 0: + stats.succ[OP_INSERT] += static_cast<uint64_t>(ds.insert(item).second); + break; + case 1: + stats.succ[OP_CONTAINS] += static_cast<uint64_t>(ds.contains(item)); + break; + case 2: + stats.succ[OP_REMOVE] += static_cast<uint64_t>(ds.remove(item)); + break; + case 3: + stats.succ[OP_FIND] += static_cast<uint64_t>(ds.find(item) != ds.end()); + break; + default: + std::terminate(); + } + ++stats.total; + } + }); +} + +template <typename Accessor> +void GenerateData(Accessor &acc) { + for (uint64_t i = 0; i <= FLAGS_max_element; ++i) { + MG_ASSERT(acc.insert(i).second); + } +} + +template <typename Accessor> +void AsserData(Accessor &acc) { + if constexpr (std::is_same_v<Accessor, memgraph::utils::SkipList<uint64_t>>) { + uint64_t val = 0; + for (auto item : acc) { + MG_ASSERT(item == val); + ++val; + } + MG_ASSERT(val == FLAGS_max_element + 1); + } else { + MG_ASSERT(acc.size() == FLAGS_max_element + 1); + } +} + +int main(int argc, char **argv) { + gflags::ParseCommandLineFlags(&argc, &argv, true); + + if (FLAGS_ds == "skip_list") { + std::cout << "#### Testing skip list" << std::endl; + memgraph::utils::SkipList<uint64_t> list; + auto acc = list.access(); + + GenerateData(acc); + AsserData(acc); + + RunDSTest(acc); + } else if (FLAGS_ds == "map") { + std::cout << "#### Testing map" << std::endl; + MapStructure<uint64_t> map; + + GenerateData(map); + AsserData(map); + + RunDSTest(map); + } else if (FLAGS_ds == "bpp") { + std::cout << "#### Testing B+ tree" << std::endl; + BppStructure<uint64_t> bpp; + + GenerateData(bpp); + AsserData(bpp); + + RunDSTest(bpp); + } else { + throw std::runtime_error("Select an existing data structure!"); + } + + return 0; +} diff --git a/tests/benchmark/skip_list_common.hpp b/tests/benchmark/skip_list_common.hpp index 988a2dfb7..8dd4c705b 100644 --- a/tests/benchmark/skip_list_common.hpp +++ b/tests/benchmark/skip_list_common.hpp @@ -1,4 +1,4 @@ -// Copyright 2021 Memgraph Ltd. +// Copyright 2022 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source @@ -81,3 +81,27 @@ inline void RunConcurrentTest(std::function<void(std::atomic<bool> *, Stats *)> std::cout << "Total successful: " << tot << " (" << tot / FLAGS_duration << " calls/s)" << std::endl; std::cout << "Total ops: " << tops << " (" << tops / FLAGS_duration << " calls/s)" << std::endl; } + +inline void RunTest(std::function<void(const std::atomic<bool> &, Stats &)> test_func) { + Stats stats; + std::atomic<bool> run{true}; + + { + std::jthread bg_thread(test_func, std::cref(run), std::ref(stats)); + std::this_thread::sleep_for(std::chrono::seconds(FLAGS_duration)); + run.store(false, std::memory_order_relaxed); + } + + std::cout << " Operations: " << stats.total << std::endl; + std::cout << " Successful insert: " << stats.succ[0] << std::endl; + std::cout << " Successful contains: " << stats.succ[1] << std::endl; + std::cout << " Successful remove: " << stats.succ[2] << std::endl; + std::cout << " Successful find: " << stats.succ[3] << std::endl; + std::cout << std::endl; + + const auto tot = stats.succ[0] + stats.succ[1] + stats.succ[2] + stats.succ[3]; + const auto tops = stats.total; + + std::cout << "Total successful: " << tot << " (" << tot / FLAGS_duration << " calls/s)" << std::endl; + std::cout << "Total ops: " << tops << " (" << tops / FLAGS_duration << " calls/s)" << std::endl; +}