diff --git a/include/storage/unique_object_store.h b/include/storage/unique_object_store.h new file mode 100644 index 000000000..8aae73c11 --- /dev/null +++ b/include/storage/unique_object_store.h @@ -0,0 +1,77 @@ +// +// Copyright 2017 Memgraph +// Created by Florijan Stamenkovic on 23.01.17. +// + +#pragma once + +#include +#include +#include +#include + +#include "data_structures/concurrent/concurrent_map.hpp" + +/** + * Storage for ensuring object uniqueness. Returns the same + * Key for all equal objects, of which only one is stored + * in this data structure. This data structure supports + * concurrent access with the above stated guarantees. + * + * Useful for both reducing the number of equal objects for which + * references are kept, and reducing the footprint of such objects + * (Keys are typically 4 byte unsigned ints). + * + * IMPORTANT: The store currently does not handle object lifetimes: + * they live forever. In many intended use cases this is fine. When + * it becomes necessary a garbage collection over this structure + * can be implemented. + * + * @tparam TObject Type of object managed by this store. + * @tparam TKey Type of key. + */ +template +class UniqueObjectStore { + +public: + // Key is the type returned by this class, it's a template param + using Key = TKey; + + UniqueObjectStore() : next_key_counter_(0) {} + + // atomic member var will ensure that we can't move nor copy this + + ~UniqueObjectStore() = default; + + /** + * Returns a Key for the given name. If a Key already exists for + * that name in this UniqueObjectStore, it is returned. Otherwise a new Key + * is generated and returned. + * + * @param obj The object for which we seek a Key + * @return Key See above. + */ + Key GetKey(const TObject &obj) { + + auto accessor = storage_.access(); + + // try to find an existing key + auto found = accessor.find(obj); + if (found != accessor.end()) + return found->second; + + // failed to find an existing key, create and insert a new one + // return insertion result (either the new key, or an existing one + // for the same value) + return accessor.insert(obj, next_key_counter_++).first->second; + } + +private: + // maps objects to keys with support for concurrent writes + ConcurrentMap storage_; + + // counter of keys that have been generated + // note that this is not necessary the count of all keys being used since + // it's possible that Keys were generated and then discarded due to concurrency + std::atomic next_key_counter_; +}; diff --git a/tests/unit/unique_object_store.cpp b/tests/unit/unique_object_store.cpp new file mode 100644 index 000000000..61982e134 --- /dev/null +++ b/tests/unit/unique_object_store.cpp @@ -0,0 +1,63 @@ +#include +#include +#include + +#include "gtest/gtest.h" + +#include "utils/total_ordering.hpp" +#include "storage/unique_object_store.h" + +/** + * Wraps an int and implements total ordering. Used for testing the + * UniqueObjectStore with custom classes. + */ +class IntWrapper : TotalOrdering, TotalOrdering +{ + +public: + IntWrapper(const int i) : i_(i) {} + + friend bool operator==(const IntWrapper &lhs, const IntWrapper &rhs) { + return lhs.i_ == rhs.i_; + } + + friend bool operator<(const IntWrapper &lhs, const IntWrapper &rhs) { + return lhs.i_ < rhs.i_; + } + +private: + const int i_; +}; + + +TEST(UniqueObjectStoreTest, GetKey) { + + UniqueObjectStore store; + EXPECT_EQ(store.GetKey("name1"), store.GetKey("name1")); + EXPECT_NE(store.GetKey("name1"), store.GetKey("name2")); + EXPECT_EQ(store.GetKey("name2"), store.GetKey("name2")); +} + +TEST(UniqueObjectStoreTest, CustomClass) { + + UniqueObjectStore store; + EXPECT_EQ(store.GetKey(42), store.GetKey(IntWrapper(42))); + EXPECT_EQ(store.GetKey(1000), store.GetKey(IntWrapper(1000))); + EXPECT_NE(store.GetKey(IntWrapper(1000)), store.GetKey(IntWrapper(42))); +} + +TEST(UniqueObjectStoreTest, RandomAccess) { + + UniqueObjectStore store; + for (int i = 0 ; i < 50 * 50 * 4 ; i++) { + int rand1 = rand() % 50; + int rand2 = rand() % 50; + EXPECT_EQ(store.GetKey(rand1) == store.GetKey(rand2), rand1 == rand2); + } +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +}