Copy-on-write added to POC
Reviewers: buda, mislav.bradac Reviewed By: mislav.bradac Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D848
This commit is contained in:
parent
0c186fe76f
commit
3ca9acb90a
106
poc/utils/copy_on_write.hpp
Normal file
106
poc/utils/copy_on_write.hpp
Normal file
@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "utils/assert.hpp"
|
||||
|
||||
namespace utils {
|
||||
|
||||
/**
|
||||
* Helper class for implementing copy-on-write member variables. Memory
|
||||
* management is automatic via shared-ptr: it is allowed for the original
|
||||
* owner to expire, the content will remain valid in copies.
|
||||
*
|
||||
* This class is generally not thread-safe. However, it is intended for use in
|
||||
* Memgraph's MVCC which is concurrent, but with specific guarantees that also
|
||||
* make it possible to use this. These guarantees are:
|
||||
* 1. Writing can only happen when there are no copies (no parallel reads). An
|
||||
* implication of this is that an obtained copy is immutable, even though in
|
||||
* general CopyOnWrite does not guarantee this (generally the owner's
|
||||
* modificatins will be visible to the copies).
|
||||
* 2. Copies are not created in parallel. MVCC guarantees this with
|
||||
* record-locking, but should be in fact legal in CopyOnWrite.
|
||||
*
|
||||
* @tparam TElement - type of content. Must be copy-constructable.
|
||||
*/
|
||||
template <typename TElement>
|
||||
class CopyOnWrite {
|
||||
public:
|
||||
/**
|
||||
* Creates a new CopyOnWrite that owns it's element.
|
||||
*
|
||||
* @param args - Arguments forwarded to the TElement constructor.
|
||||
* @tparam TArgs - Argument types.
|
||||
*/
|
||||
template <typename... TArgs>
|
||||
CopyOnWrite(TArgs &&... args) {
|
||||
TElement *new_element = new TElement(std::forward<TArgs>(args)...);
|
||||
element_ptr_.reset(new_element);
|
||||
is_owner_ = true;
|
||||
}
|
||||
|
||||
/** Creates a copy of the given CopyOnWrite object that does not copy the
|
||||
* element and does not assume ownership over it. */
|
||||
CopyOnWrite(const CopyOnWrite &other)
|
||||
: element_ptr_{other.element_ptr_}, is_owner_{false} {}
|
||||
|
||||
/** Creates a copy of the given CopyOnWrite object that does not copy the
|
||||
* element and does not assume ownership over it. This is a non-const
|
||||
* reference accepting copy constructor. The hack is necessary to prevent the
|
||||
* variadic constructor from being a better match for non-const CopyOnWrite
|
||||
* argument. */
|
||||
CopyOnWrite(CopyOnWrite &other)
|
||||
: CopyOnWrite(const_cast<const CopyOnWrite &>(other)) {}
|
||||
|
||||
/** Creates a copy of the given temporary CyopOnWrite object. If the temporary
|
||||
* is owner then ownership is transferred to this CopyOnWrite. Otherwise this
|
||||
* CopyOnWrite does not become the owner. */
|
||||
CopyOnWrite(CopyOnWrite &&other) = default;
|
||||
|
||||
/** Copy assignment of another CopyOnWrite. Does not transfer ownership (this
|
||||
* CopyOnWrite is not the owner). */
|
||||
CopyOnWrite &operator=(const CopyOnWrite &other) {
|
||||
element_ptr_ = other.element_ptr_;
|
||||
is_owner_ = false;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/** Copy assignment of a temporary CopyOnWrite. If the temporary is owner then
|
||||
* ownerships is transferred to this CopyOnWrite. Otherwise this CopyOnWrite
|
||||
* does not become the owner. */
|
||||
CopyOnWrite &operator=(CopyOnWrite &&other) = default;
|
||||
|
||||
// All the dereferencing operators are overloaded to return a const element
|
||||
// reference. There is no differentiation between const and non-const member
|
||||
// function behavior because an implicit copy creation on non-const
|
||||
// dereferencing would most likely result in excessive copying. For that
|
||||
// reason
|
||||
// an explicit call to the `Write` function is required to obtain a non-const
|
||||
// reference to element.
|
||||
const TElement &operator*() { return *element_ptr_; }
|
||||
const TElement &operator*() const { return *element_ptr_; }
|
||||
const TElement &get() { return *element_ptr_; }
|
||||
const TElement &get() const { return *element_ptr_; }
|
||||
const TElement *operator->() { return element_ptr_.get(); }
|
||||
const TElement *operator->() const { return element_ptr_.get(); }
|
||||
|
||||
/** Indicates if this CopyOnWrite object is the owner of it's element. */
|
||||
bool is_owner() const { return is_owner_; };
|
||||
|
||||
/**
|
||||
* If this CopyOnWrite is the owner of it's element, a non-const reference to
|
||||
* is returned. If this CopyOnWrite is not the owner, then the element is
|
||||
* copied and this CopyOnWrite becomes the owner.
|
||||
*/
|
||||
TElement &Write() {
|
||||
if (is_owner_) return *element_ptr_;
|
||||
element_ptr_ = std::shared_ptr<TElement>(new TElement(*element_ptr_));
|
||||
is_owner_ = true;
|
||||
return *element_ptr_;
|
||||
};
|
||||
|
||||
private:
|
||||
std::shared_ptr<TElement> element_ptr_;
|
||||
bool is_owner_{false};
|
||||
};
|
||||
} // namespace utils
|
67
poc/utils/utils_copy_on_write.cpp
Normal file
67
poc/utils/utils_copy_on_write.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "utils/copy_on_write.hpp"
|
||||
|
||||
TEST(CopyOneWrite, Dereferencing) {
|
||||
utils::CopyOnWrite<std::string> number("42");
|
||||
EXPECT_EQ((*number).size(), 2);
|
||||
EXPECT_EQ(number.get().size(), 2);
|
||||
EXPECT_EQ(number->size(), 2);
|
||||
}
|
||||
|
||||
TEST(CopyOneWrite, Ownership) {
|
||||
utils::CopyOnWrite<std::string> number("42");
|
||||
EXPECT_TRUE(number.is_owner());
|
||||
|
||||
utils::CopyOnWrite<std::string> copy_constructed(number);
|
||||
auto copy_assigned = number;
|
||||
EXPECT_FALSE(copy_constructed.is_owner());
|
||||
EXPECT_FALSE(copy_assigned.is_owner());
|
||||
EXPECT_TRUE(number.is_owner());
|
||||
}
|
||||
|
||||
TEST(CopyOneWrite, OwnershipFromTemporary) {
|
||||
utils::CopyOnWrite<std::string> copy_constructed(
|
||||
utils::CopyOnWrite<std::string>("42"));
|
||||
auto copy_assigned = utils::CopyOnWrite<std::string>("42");
|
||||
EXPECT_TRUE(copy_constructed.is_owner());
|
||||
EXPECT_TRUE(copy_assigned.is_owner());
|
||||
}
|
||||
|
||||
struct DestructorCounter {
|
||||
DestructorCounter(int &counter) : counter_(counter) {}
|
||||
~DestructorCounter() {
|
||||
counter_++;
|
||||
}
|
||||
private:
|
||||
int &counter_;
|
||||
};
|
||||
|
||||
TEST(CopyOnWrite, ElementDestruction) {
|
||||
int counter = 0;
|
||||
std::vector<utils::CopyOnWrite<DestructorCounter>> initial_owner;
|
||||
initial_owner.emplace_back(counter);
|
||||
{
|
||||
auto copy = initial_owner[0];
|
||||
EXPECT_EQ(counter, 0);
|
||||
initial_owner.clear();
|
||||
EXPECT_EQ(counter, 0);
|
||||
}
|
||||
EXPECT_EQ(counter, 1);
|
||||
}
|
||||
|
||||
TEST(CopyOneWrite, Copy) {
|
||||
utils::CopyOnWrite<std::string> number("42");
|
||||
auto copy = number;
|
||||
EXPECT_EQ(*copy, "42");
|
||||
|
||||
// modification by owner
|
||||
number.Write().resize(1);
|
||||
EXPECT_EQ(*number, "4");
|
||||
EXPECT_EQ(*copy, "4");
|
||||
|
||||
// modification by copy
|
||||
copy.Write().resize(2, '3');
|
||||
EXPECT_EQ(*number, "4");
|
||||
EXPECT_EQ(*copy, "43");
|
||||
}
|
Loading…
Reference in New Issue
Block a user