Added Skiplist ReverseIterator, Distance Approximation Prototype and documented some stuff

Summary: Skiplist ReverseIterator and Distance Approximation

Test Plan: manual

Reviewers: florijan, buda

Reviewed By: buda

Subscribers: pullbot, florijan, buda

Differential Revision: https://phabricator.memgraph.io/D44
This commit is contained in:
sale 2017-01-31 13:48:39 +01:00
parent 9135e2fa7a
commit 99b8a4f234
7 changed files with 1595 additions and 913 deletions

File diff suppressed because it is too large Load Diff

View File

@ -4,21 +4,28 @@
#include "threading/sync/spinlock.hpp"
/**
* @class Lockable
*
* @brief
* Lockable is used as an custom implementation of a mutex mechanism. It is
* implemented as a wrapper around std::lock_guard and std::unique_guard with
* a default lock called Spinlock.
*
* @tparam lock_t type of lock to be used (default = Spinlock)
*/
template <class lock_t = SpinLock>
class Lockable
{
public:
using lock_type = lock_t;
class Lockable {
public:
using lock_type = lock_t;
std::lock_guard<lock_t> acquire_guard() const
{
return std::lock_guard<lock_t>(lock);
}
std::lock_guard<lock_t> acquire_guard() const {
return std::lock_guard<lock_t>(lock);
}
std::unique_lock<lock_t> acquire_unique() const
{
return std::unique_lock<lock_t>(lock);
}
std::unique_lock<lock_t> acquire_unique() const {
return std::unique_lock<lock_t>(lock);
}
mutable lock_t lock;
mutable lock_t lock;
};

View File

@ -1,23 +1,28 @@
#pragma once
#include <atomic>
#include <unistd.h>
#include <atomic>
#include "utils/cpu_relax.hpp"
class SpinLock
{
public:
void lock()
{ // Before was memorz_order_acquire
while (lock_flag.test_and_set(std::memory_order_seq_cst))
cpu_relax();
/// usleep(250);
}
// Before was memory_order_release
void unlock() { lock_flag.clear(std::memory_order_seq_cst); }
/**
* @class SpinLock
*
* @brief
* Spinlock is used as an locking mechanism based on an atomic flag and
* waiting loops. It uses the cpu_relax "asm pause" command to optimize wasted
* time while the threads are waiting.
*
*/
class SpinLock {
public:
void lock() { // Before was memory_order_acquire
while (lock_flag.test_and_set(std::memory_order_seq_cst)) cpu_relax();
}
// Before was memory_order_release
void unlock() { lock_flag.clear(std::memory_order_seq_cst); }
private:
// guaranteed by standard to be lock free!
mutable std::atomic_flag lock_flag = ATOMIC_FLAG_INIT;
private:
// guaranteed by standard to be lock free!
mutable std::atomic_flag lock_flag = ATOMIC_FLAG_INIT;
};

View File

@ -1,56 +1,90 @@
#pragma once
#include <ext/aligned_buffer.h>
#include <utility>
#include "utils/assert.hpp"
#include <ext/aligned_buffer.h>
/**
* @class Placeholder
*
* @brief
* Placeholder is used to allocate memory for an object on heap providing
* methods for setting and getting the object and making sure that the
* object is initialized.
*
* @tparam T type of object to be wrapped in the placeholder
*/
template <class T>
class Placeholder
{
public:
Placeholder() = default;
class Placeholder {
public:
Placeholder() = default;
Placeholder(Placeholder &) = delete;
Placeholder(Placeholder &&) = delete;
Placeholder(Placeholder &) = delete;
Placeholder(Placeholder &&) = delete;
~Placeholder()
{
if (initialized) get().~T();
};
/**
* The destructor automatically calls the wrapped objects destructor.
*/
~Placeholder() {
if (initialized) get().~T();
};
bool is_initialized() { return initialized; }
/**
* @return returns true if object is set in memory otherwise false.
*/
bool is_initialized() { return initialized; }
T &get() noexcept
{
assert(initialized);
return *data._M_ptr();
}
T &get() noexcept {
runtime_assert(initialized, "Placeholder object not initialized");
return *data._M_ptr();
}
const T &get() const noexcept
{
assert(initialized);
return *data._M_ptr();
}
/**
* @return const reference to object.
*/
const T &get() const noexcept {
runtime_assert(initialized, "Placeholder object not initialized");
return *data._M_ptr();
}
void set(const T &item)
{
new (data._M_addr()) T(item);
initialized = true;
}
/**
* Sets item in allocated memory and sets the initialized flag.
*
* @param T& item reference to the item initialized in allocated memory
*/
void set(const T &item) {
new (data._M_addr()) T(item);
initialized = true;
}
void set(T &&item)
{
new (data._M_addr()) T(std::move(item));
initialized = true;
}
/**
* Moves item to allocated memory and sets initialized flag.1
*
* @param T&& rvalue reference to the item which is moved to allocated memory
*/
void set(T &&item) {
new (data._M_addr()) T(std::move(item));
initialized = true;
}
template <class... Args>
void emplace(Args &&... args)
{
new (data._M_addr()) T(args...);
initialized = true;
}
/**
* Emplaces item to allocated memory and calls the Constructor with specified
* arguments.
*
* @tparam Args type of arguments to be passed to the objects constructor.
* @param Parameters passed to the objects constructor.
*/
template <class... Args>
void emplace(Args &&... args) {
new (data._M_addr()) T(args...);
initialized = true;
}
private:
__gnu_cxx::__aligned_buffer<T> data;
bool initialized = false;
private:
// libstd aligned buffer struct
__gnu_cxx::__aligned_buffer<T> data;
bool initialized = false;
};

View File

@ -1,4 +1,5 @@
#include <random>
#include <set>
#include <vector>
// namespace ::utils
@ -71,5 +72,15 @@ auto generate_vector(RandomGenerator &gen, int size) {
return elements;
}
// IMPORTANT
// be careful with RandomGenerator ranges and set size
// condition must be valid: size(set) << range(RandomGenerator)
template <class RandomGenerator>
auto generate_set(RandomGenerator &gen, int size) {
std::set<decltype(gen.next())> elements;
while (elements.size() < size) elements.insert(gen.next());
return elements;
}
}; // namespace utils::random
}; // namespace ::utils

View File

@ -0,0 +1,109 @@
/**
@date: 2017-01-31
@authors: Sandi Fatic
These tests are used to benchmark the ReverseIterator vs the Find function
while iterating the whole skiplist in reverse.
*/
#include <algorithm>
#include <thread>
#include <vector>
#include "benchmark/benchmark_api.h"
#include "data_structures/concurrent/skiplist.hpp"
#include "logging/default.hpp"
#include "logging/streams/stdout.hpp"
#include "utils/random/generator.h"
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));
int counter = 10;
auto accessor = skiplist.access();
auto rbegin = accessor.rbegin();
auto rend = accessor.rend();
state.ResumeTiming();
for (int i = 0; i < counter; i++) {
if (rbegin != rend) {
rbegin++;
}
}
}
}
static void FindFromRBegin(benchmark::State &state) {
while (state.KeepRunning()) {
state.PauseTiming();
SkipList<int> skiplist;
InsertConcurrentSkiplist(&skiplist, 0, state.range(0));
int counter = 10;
auto accessor = skiplist.access();
auto rbegin = accessor.rbegin();
auto rend = accessor.rend();
state.ResumeTiming();
for (int i = 0; i < counter; i++) {
accessor.find(*rbegin - i);
}
}
}
auto BM_ReverseFromRBegin = [](benchmark::State &state) {
ReverseFromRBegin(state);
};
auto BM_FindFromRBegin = [](benchmark::State &state) { FindFromRBegin(state); };
int main(int argc, char **argv) {
logging::init_async();
logging::log->pipe(std::make_unique<Stdout>());
benchmark::RegisterBenchmark("ReverseFromRBegin", BM_ReverseFromRBegin)
->RangeMultiplier(2)
->Range(1 << 10, 1 << 16);
benchmark::RegisterBenchmark("FindFromRBegin", BM_FindFromRBegin)
->RangeMultiplier(2)
->Range(1 << 10, 1 << 16);
benchmark::RunSpecifiedBenchmarks();
return 0;
}

View File

@ -0,0 +1,420 @@
/**
@date: 2017-01-2017
@authors: Sandi Fatic
@brief
These tests are used to test the functionality of the reverse() function in
a single threaded scenario. For more concurrent tests look in the concurrent
testing folder.
@todo
Concurrent tests are missing for now.
*/
#include <algorithm>
#include "gtest/gtest.h"
#include "data_structures/concurrent/skiplist.hpp"
#include "logging/default.cpp"
#include "utils/random/generator.h"
using utils::random::NumberGenerator;
using IntegerGenerator = NumberGenerator<std::uniform_int_distribution<int>,
std::default_random_engine, int>;
/*
Tests Skiplist rbegin() and rend() iterators on a sequential dataset.
*/
TEST(SkipListReverseIteratorTest, SequentialIteratorsBeginToEnd) {
SkipList<int> skiplist;
auto accessor = skiplist.access();
int number_of_elements = 1024 * 16;
for (int i = 0; i < number_of_elements; i++) accessor.insert(std::move(i));
auto rbegin = accessor.rbegin();
auto rend = accessor.rend();
while (rbegin != rend) {
ASSERT_EQ(number_of_elements - 1, *rbegin);
rbegin++;
number_of_elements--;
}
}
/*
Tests Skiplist rbegin() and rend() iterators on a random dataset.
*/
TEST(SkipListReverseIteratorTest, RandomIteratorsBeginToEnd) {
SkipList<int> skiplist;
auto accessor = skiplist.access();
int number_of_elements = 1024 * 16;
IntegerGenerator generator(0, 1000000000);
std::set<int> elems_set =
utils::random::generate_set(generator, number_of_elements);
int end = elems_set.size();
std::vector<int> elems_descending(end);
for (auto el : elems_set) {
end--;
accessor.insert(std::move(el));
elems_descending[end] = el;
}
auto rbegin = accessor.rbegin();
auto rend = accessor.rend();
while (rbegin != rend) {
ASSERT_EQ(elems_descending[end], *rbegin);
end++;
rbegin++;
}
}
/*
Tests Skiplist reverse() when element exists. The skiplist uses a sequential
dataset and the element provided exists is in range of the dataset. The
reverse function should return an std::pair<iterator_to_element_before, true>.
*/
TEST(SkipListReverseIteratorTest, SequentialIteratorsElementExists) {
SkipList<int> skiplist;
auto accessor = skiplist.access();
int number_of_elements = 1024 * 16;
for (int i = 0; i < number_of_elements; i++) accessor.insert(std::move(i));
int element = 1024 * 8;
auto reverse_pair = accessor.reverse(element);
ASSERT_EQ(reverse_pair.second, true);
auto rbegin = reverse_pair.first;
auto rend = accessor.rend();
while (rbegin != rend) {
ASSERT_EQ(element - 1, *rbegin);
rbegin++;
element--;
}
}
/*
Tests Skiplist reverse() when element exists. The skiplist uses a random
dataset and the element provide exists in the random dataset. The reverse
function should return an std::pair<iterator_to_element_before, true>.
*/
TEST(SkipListReverseIteratorTest, RandomIteratorsElementExists) {
SkipList<int> skiplist;
auto accessor = skiplist.access();
int number_of_elements = 1024 * 16;
IntegerGenerator generator(0, 1000000000);
std::set<int> elems_set =
utils::random::generate_set(generator, number_of_elements);
int end = elems_set.size();
std::vector<int> elems_descending(end);
for (auto el : elems_set) {
end--;
accessor.insert(std::move(el));
elems_descending[end] = el;
}
int middle = end / 2;
auto reverse_pair = accessor.reverse(elems_descending[middle]);
ASSERT_EQ(reverse_pair.second, true);
auto rbegin = reverse_pair.first;
auto rend = accessor.rend();
while (rbegin != rend) {
ASSERT_EQ(elems_descending[middle + 1], *rbegin);
middle++;
rbegin++;
}
}
/*
Tests Skiplist reverse() when element exists and the element is the first one
in the skiplist. The skiplist uses a sequential dataset. The reverse function
should return an std::pair<rend, true>.
*/
TEST(SkipListReverseIteratorTest, SequentialIteratorsMinimumElement) {
SkipList<int> skiplist;
auto accessor = skiplist.access();
int number_of_elements = 1024 * 16;
for (int i = 0; i < number_of_elements; i++) accessor.insert(std::move(i));
auto reverse_pair = accessor.reverse(0);
ASSERT_EQ(reverse_pair.second, true);
auto rbegin = reverse_pair.first;
auto rend = accessor.rend();
ASSERT_EQ(rbegin, rend);
}
/*
Tests Skiplist reverse() when element exists and the element is the first one
in the skiplist. The skiplist uses a random dataset. The reverse function
should return an std::pair<rend, true>.
*/
TEST(SkipListReverseIteratorTest, RandomIteratorsMinimumElement) {
SkipList<int> skiplist;
auto accessor = skiplist.access();
int number_of_elements = 1024 * 16;
IntegerGenerator generator(0, 1000000000);
std::set<int> elems_set =
utils::random::generate_set(generator, number_of_elements);
auto min_el = std::min_element(elems_set.begin(), elems_set.end());
for (auto el : elems_set) accessor.insert(std::move(el));
auto reverse_pair = accessor.reverse(*min_el);
ASSERT_EQ(reverse_pair.second, true);
auto rbegin = reverse_pair.first;
auto rend = accessor.rend();
ASSERT_EQ(rbegin, rend);
}
/*
Tests Skiplist reverse() when element exists and the element is the last one
in the skiplist. The skiplist uses a sequential dataset. The reverse function
should return an std::pair<iterator_to_smaller_element, true>.
*/
TEST(SkipListReverseIteratorTest, SequentialIteratorsMaximumElement) {
SkipList<int> skiplist;
auto accessor = skiplist.access();
int number_of_elements = 1024 * 16;
for (int i = 0; i < number_of_elements; i++) accessor.insert(std::move(i));
auto reverse_pair = accessor.reverse(number_of_elements - 1);
ASSERT_EQ(reverse_pair.second, true);
auto rbegin = reverse_pair.first;
auto rbeing_real = accessor.rbegin();
rbeing_real++;
ASSERT_EQ(rbegin, rbeing_real);
}
/*
Tests Skiplist reverse() when element exists and the element is the last one
in the skiplist. the skiplist uses a random dataset. The reverse function
should return and std::pair,iterator_to_smaller_element, true>.
*/
TEST(SkipListReverseIteratorTest, RandomIteratorsMaximumElement) {
SkipList<int> skiplist;
auto accessor = skiplist.access();
int number_of_elements = 1024 * 16;
IntegerGenerator generator(0, 1000000000);
std::set<int> elems_set =
utils::random::generate_set(generator, number_of_elements);
auto max_el = std::max_element(elems_set.begin(), elems_set.end());
for (auto el : elems_set) accessor.insert(std::move(el));
auto reverse_pair = accessor.reverse(*max_el);
ASSERT_EQ(reverse_pair.second, true);
auto rbegin = reverse_pair.first;
auto rbeing_real = accessor.rbegin();
rbeing_real++;
ASSERT_EQ(rbegin, rbeing_real);
}
/*
Tests Skipslist reverse() when element out of bounds. The skiplist uses a
sequential dataset and the element provided is bigger then the last element
in skiplist. Reverse function should return an std::pair<rend, false>.
*/
TEST(SkipListReverseIteratorTest, SequentialIteratorsElementBiggerThanLast) {
SkipList<int> skiplist;
auto accessor = skiplist.access();
int number_of_elements = 1024 * 16;
for (int i = 0; i < number_of_elements; i++) accessor.insert(std::move(i));
auto reverse_pair = accessor.reverse(number_of_elements + 1);
ASSERT_EQ(reverse_pair.second, false);
auto rbegin = reverse_pair.first;
auto rend = accessor.rend();
ASSERT_EQ(rend, rbegin);
}
/*
Tests Skipslist reverse() when element out of bounds. The skiplist uses a
random dataset and the element provide is bigger then the last element in the
skiplist. Reverse function should return an std::pair<rend, false>.
*/
TEST(SkipListReverseIteratorTest, RandomIteratorsElementBiggerThanLast) {
SkipList<int> skiplist;
auto accessor = skiplist.access();
int number_of_elements = 1024 * 16;
IntegerGenerator generator(0, 1000000000);
std::set<int> elems_set =
utils::random::generate_set(generator, number_of_elements);
auto max_el = std::max_element(elems_set.begin(), elems_set.end());
for (auto el : elems_set) accessor.insert(std::move(el));
auto reverse_pair = accessor.reverse(*max_el + 1);
ASSERT_EQ(reverse_pair.second, false);
auto rbegin = reverse_pair.first;
auto rend = accessor.rend();
ASSERT_EQ(rend, rbegin);
}
/*
Tests Skipslist reverse() when element out of bounds.
The skiplist uses a sequential dataset and the element provided is lower
then the first element in skiplist. Reverse function should return an
std::pair<rend, false>.
*/
TEST(SkipListReverseIteratorTest, SequentialIteratorsElementLowerThanFirst) {
SkipList<int> skiplist;
auto accessor = skiplist.access();
int number_of_elements = 1024 * 16;
for (int i = 0; i < number_of_elements; i++) accessor.insert(std::move(i));
auto reverse_pair = accessor.reverse(-1);
ASSERT_EQ(reverse_pair.second, false);
auto rbegin = reverse_pair.first;
auto rend = accessor.rend();
ASSERT_EQ(rend, rbegin);
}
/*
Tests Skiplist reverse() when element out of bounds. The skiplist uses a
random dataset and the element provided is lower then the first element in
skiplist. Reverse function should return an std::pair<rend, false>.
*/
TEST(SkipListReverseIteratorTest, RandomIteratorsElementLowerThanFirst) {
SkipList<int> skiplist;
auto accessor = skiplist.access();
int number_of_elements = 1024 * 16;
IntegerGenerator generator(0, 1000000000);
std::set<int> elems_set =
utils::random::generate_set(generator, number_of_elements);
auto min_el = std::min_element(elems_set.begin(), elems_set.end());
for (auto el : elems_set) accessor.insert(std::move(el));
auto reverse_pair = accessor.reverse(*min_el - 1);
ASSERT_EQ(reverse_pair.second, false);
auto rbegin = reverse_pair.first;
auto rend = accessor.rend();
ASSERT_EQ(rend, rbegin);
}
/*
Tests Skiplist ReverseIterator when concurrently inserting an element while
iterating. The inserted element should also be traversed.
*/
TEST(SkipListReverseIteratorTest, InsertWhileIteratingTest) {
SkipList<int> skiplist;
auto accessor = skiplist.access();
int number_of_elements = 1024 * 16;
for (int i = 1; i < number_of_elements; i+=2) accessor.insert(std::move(i));
auto rbegin = accessor.rbegin();
auto rend = accessor.rend();
for (int i = 0; i < number_of_elements; i+=2) accessor.insert(std::move(i));
int element = number_of_elements - 1;
while (rbegin != rend) {
ASSERT_EQ(element, *rbegin);
rbegin++;
element--;
}
}
/*
Tests Skiplist ReverseIterator when concurrently deleting an element while
iterating. The deleted element shouldn't be traversed except if the element
is deleted while pointing to the element.
*/
TEST(SkipListReverseIteratorTest, DeleteWhileIteratingTest) {
SkipList<int> skiplist;
auto accessor = skiplist.access();
int number_of_elements = 1024 * 16;
for (int i = 0; i < number_of_elements; i++) accessor.insert(std::move(i));
auto rbegin = accessor.rbegin();
auto rend = accessor.rend();
int element = number_of_elements - 2;
// check element which will be deleted
rbegin++;
ASSERT_EQ(element, *rbegin);
// delete elements
for (int i = 0; i < number_of_elements; i+=2) accessor.remove(i);
// check if still points to the same after delete
ASSERT_EQ(element, *rbegin);
rbegin++;
// check all deleted elements after
while (rbegin != rend && element > 0) {
ASSERT_EQ(element-1, *rbegin);
rbegin++;
element-=2;
}
}
int main(int argc, char** argv) {
logging::init_sync();
logging::log->pipe(std::make_unique<Stdout>());
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}