diff --git a/tests/benchmark/data_structures/concurrent/skiplist_helper.hpp b/tests/benchmark/data_structures/concurrent/skiplist_helper.hpp new file mode 100644 index 000000000..e9be008a6 --- /dev/null +++ b/tests/benchmark/data_structures/concurrent/skiplist_helper.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include <algorithm> +#include <atomic> +#include <chrono> +#include <thread> +#include <vector> + +#include "data_structures/concurrent/skiplist.hpp" + +/** + * Helper functions for skiplist. This functions are used to insert + * concurrently into skiplist. + */ +class SkipListHelper { + public: + /** + * Inserts into a skiplist concurrently. Tries to synchronize all threads to + * start and end in the same time. This function should only be used to + * benchmark skiplist in a way that doesn't give thread a chance to consume + * more than the (end - start) / num_of_threads elements in the allocated + * time, since that would make the measurement inaccurate. Also shuffles the + * data to avoid consecutive value inserts. + * + * @param skiplist - skiplist instance + * @param start - value_range start + * @param end - value_range end (exclusive) + * @param num_of_threads - number of threads to insert with + * @param duration - duration of thread time in microseconds + * @return number of inserted elements + */ + static int InsertConcurrentSkiplistTimed( + SkipList<int> *skiplist, const int start, const int end, + const int num_of_threads, const std::chrono::microseconds &duration) { + std::vector<int> V(end - start); + for (int i = start; i < end; ++i) V[i] = i; + std::random_shuffle(V.begin(), V.end()); + std::vector<std::thread> threads; + + std::atomic<bool> stopped{1}; + std::atomic<int> count{0}; + for (int i = 0; i < num_of_threads; ++i) { + const int part = (end - start) / num_of_threads; + threads.emplace_back(std::thread( + [&V, &stopped, &count](SkipList<int> *skiplist, int start, int end) { + while (stopped) + ; + auto accessor = skiplist->access(); + for (int i = start; i < end && !stopped; ++i) { + while (accessor.insert(V[i]).second == false) + ; + ++count; + } + }, + skiplist, start + i * part, start + (i + 1) * part)); + } + stopped = false; + std::this_thread::sleep_for(duration); + stopped = true; + for (auto &x : threads) x.join(); + return count; + } + + /** + * Insert into skiplist concurrently. With the hardware maximal number of + * threads an instance will allow. + * + * @param skiplist - skiplist instance + * @param start - starting value to insert + * @param end - ending value to insert + */ + static void InsertConcurrentSkiplist(SkipList<int> *skiplist, int start, + int end) { + int number_of_threads = std::thread::hardware_concurrency(); + std::vector<std::thread> threads; + + for (int i = 0; i < number_of_threads; i++) { + const int part = (end - start) / number_of_threads; + threads.emplace_back(std::thread( + [](SkipList<int> *skiplist, int start, int end) { + auto accessor = skiplist->access(); + + for (; start < end; start++) { + while (!accessor.insert(std::move(start)).second) + ; + } + }, + skiplist, start + i * part, start + (i + 1) * part)); + } + + for (auto &thread : threads) thread.join(); + } + + private: +}; diff --git a/tests/benchmark/data_structures/concurrent/skiplist_insert.cpp b/tests/benchmark/data_structures/concurrent/skiplist_insert.cpp new file mode 100644 index 000000000..2a3b5d4de --- /dev/null +++ b/tests/benchmark/data_structures/concurrent/skiplist_insert.cpp @@ -0,0 +1,63 @@ +#ifndef NDEBUG +#define NDEBUG +#endif + +#include <algorithm> +#include <thread> +#include <vector> + +#include "benchmark/benchmark.h" +#include "benchmark/benchmark_api.h" +#include "data_structures/concurrent/skiplist.hpp" +#include "logging/default.hpp" +#include "logging/streams/stderr.hpp" +#include "skiplist_helper.hpp" +#include "utils/assert.hpp" + +void Insert(benchmark::State& state) { + SkipList<int> skiplist; + while (state.KeepRunning()) { + const int count = SkipListHelper::InsertConcurrentSkiplistTimed( + &skiplist, 0, 10000000, state.range(1), + std::chrono::microseconds(state.range(0))); + state.SetItemsProcessed(count); // Number of processed items in one + // iteration - useful for items/per s. + state.SetIterationTime(state.range(0) * 1.0 / + 1000000); // Time the iteration took - since ideally + // all threads should run and stop at the + // same time we set the time manually. + auto sl_access = skiplist.access(); + while (sl_access.size()) sl_access.remove(*sl_access.begin()); + } +} + +/** + * Invokes the test function with two arguments, time and number of threads. + * Time is specified in microseconds. + */ +static void CustomArguments(benchmark::internal::Benchmark* b) { + for (int i = (1 << 18); i <= (1 << 20); i *= 2) + for (int j = 1; j <= 8; ++j) b->Args({i, j}); +} + +/** + * This benchmark represents a use case of benchmarking one multi-threaded + * concurrent structure. This test assumes that all threads will start and end + * at the exact same time and will compete with each other. + */ +BENCHMARK(Insert) + ->Apply(CustomArguments) // Set custom arguments. + ->Unit(benchmark::kMicrosecond) + ->UseManualTime() // Don't calculate real-time but depend on function + // providing the execution time. + ->Repetitions(3) + ->ReportAggregatesOnly(1); + +int main(int argc, char** argv) { + logging::init_async(); + logging::log->pipe(std::make_unique<Stderr>()); + + ::benchmark::Initialize(&argc, argv); + ::benchmark::RunSpecifiedBenchmarks(); + return 0; +} diff --git a/tests/benchmark/data_structures/concurrent/skiplist_reverse_iteration.cpp b/tests/benchmark/data_structures/concurrent/skiplist_reverse_iteration.cpp index ee9a9b868..949e16824 100644 --- a/tests/benchmark/data_structures/concurrent/skiplist_reverse_iteration.cpp +++ b/tests/benchmark/data_structures/concurrent/skiplist_reverse_iteration.cpp @@ -14,41 +14,19 @@ #include "data_structures/concurrent/skiplist.hpp" #include "logging/default.hpp" #include "logging/streams/stdout.hpp" +#include "skiplist_helper.hpp" #include "utils/random/random_generator.hpp" using utils::random::NumberGenerator; using IntegerGenerator = NumberGenerator<std::uniform_int_distribution<int>, std::default_random_engine, int>; -void InsertSkiplist(SkipList<int> *skiplist, int start, int end) { - auto accessor = skiplist->access(); - - for (int start = 0; start < end; start++) { - accessor.insert(std::move(start)); - } -} - -void InsertConcurrentSkiplist(SkipList<int> *skiplist, int start, int end) { - int number_od_threads = std::thread::hardware_concurrency(); - std::vector<std::thread> threads(number_od_threads); - - for (int i = 0; i < number_od_threads; i++) { - int part = (end - start) / number_od_threads; - threads[i] = std::thread(InsertSkiplist, skiplist, start + i * part, - start + (i + 1) * part); - } - - for (int i = 0; i < number_od_threads; i++) { - threads[i].join(); - } -} - static void ReverseFromRBegin(benchmark::State &state) { while (state.KeepRunning()) { state.PauseTiming(); SkipList<int> skiplist; - InsertConcurrentSkiplist(&skiplist, 0, state.range(0)); + SkipListHelper::InsertConcurrentSkiplist(&skiplist, 0, state.range(0)); int counter = 10; auto accessor = skiplist.access(); @@ -70,7 +48,7 @@ static void FindFromRBegin(benchmark::State &state) { state.PauseTiming(); SkipList<int> skiplist; - InsertConcurrentSkiplist(&skiplist, 0, state.range(0)); + SkipListHelper::InsertConcurrentSkiplist(&skiplist, 0, state.range(0)); int counter = 10; auto accessor = skiplist.access();