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;
+}