Add move constructor and assignment operator to TypedValue

Summary:
`heaptrack` shows a miniscule decrease in memory usage during query
execution.

Running the below query on the TEDTalk database 100 times gives the
following results:

- number of allocations: from 642647 to 642589
- bytes allocated in total: from 48.79 MiB to 48.78 MiB

```
MATCH (t:Tag)<-[:HasTag]-(n:Talk)
RETURN t.name AS Tag, COUNT(n) AS TalksCount
ORDER BY TalksCount DESC, Tag LIMIT 20;
```

Regarding `TypedValue`'s destructor: we're allowed to manually destruct the
union memebers that we constructed using placement-new. However, it is undefined
behavior to call the destructor after an object's lifetime has ended. Calling
`TypedValue`'s own destructor within its assignment operator counts as ending
its lifetime, which means that the next call to its destructor will invoke
undefined behavior.

See the C++ Draft N4140, clauses 12.4/15 and 3.8/1.3.

Reviewers: teon.banek, mtomic

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D1630
This commit is contained in:
Lovro Lugovic 2018-10-09 11:36:21 +02:00
parent 4e5fe37dd6
commit 5bdb04a693
5 changed files with 261 additions and 93 deletions

View File

@ -74,7 +74,7 @@ ProduceRpcServer::OngoingProduce::PullOneFromCursor() {
if (cursor_->Pull(frame_, context_)) { if (cursor_->Pull(frame_, context_)) {
results.reserve(pull_symbols_.size()); results.reserve(pull_symbols_.size());
for (const auto &symbol : pull_symbols_) { for (const auto &symbol : pull_symbols_) {
results.emplace_back(std::move(frame_[symbol])); results.push_back(frame_[symbol]);
} }
} else { } else {
cursor_state_ = PullState::CURSOR_EXHAUSTED; cursor_state_ = PullState::CURSOR_EXHAUSTED;

View File

@ -3165,18 +3165,10 @@ class CartesianCursor : public Cursor {
} }
bool Pull(Frame &frame, Context &context) override { bool Pull(Frame &frame, Context &context) override {
auto copy_frame = [&frame]() {
std::vector<TypedValue> result;
for (auto &elem : frame.elems()) {
result.emplace_back(std::move(elem));
}
return result;
};
if (!cartesian_pull_initialized_) { if (!cartesian_pull_initialized_) {
// Pull all left_op frames. // Pull all left_op frames.
while (left_op_cursor_->Pull(frame, context)) { while (left_op_cursor_->Pull(frame, context)) {
left_op_frames_.emplace_back(copy_frame()); left_op_frames_.emplace_back(frame.elems());
} }
// We're setting the iterator to 'end' here so it pulls the right // We're setting the iterator to 'end' here so it pulls the right
@ -3201,7 +3193,7 @@ class CartesianCursor : public Cursor {
// Advance right_op_cursor_. // Advance right_op_cursor_.
if (!right_op_cursor_->Pull(frame, context)) return false; if (!right_op_cursor_->Pull(frame, context)) return false;
right_op_frame_ = copy_frame(); right_op_frame_ = frame.elems();
left_op_frames_it_ = left_op_frames_.begin(); left_op_frames_it_ = left_op_frames_.begin();
} else { } else {
// Make sure right_op_cursor last pulled results are on frame. // Make sure right_op_cursor last pulled results are on frame.

View File

@ -4,6 +4,7 @@
#include <cmath> #include <cmath>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <utility>
#include "glog/logging.h" #include "glog/logging.h"
@ -50,6 +51,51 @@ TypedValue::TypedValue(const PropertyValue &value) {
LOG(FATAL) << "Unsupported type"; LOG(FATAL) << "Unsupported type";
} }
TypedValue::TypedValue(PropertyValue &&value) {
switch (value.type()) {
case PropertyValue::Type::Null:
type_ = Type::Null;
break;
case PropertyValue::Type::Bool:
type_ = Type::Bool;
bool_v = value.Value<bool>();
break;
case PropertyValue::Type::Int:
type_ = Type::Int;
int_v = value.Value<int64_t>();
break;
case PropertyValue::Type::Double:
type_ = Type::Double;
double_v = value.Value<double>();
break;
case PropertyValue::Type::String:
type_ = Type::String;
// TODO: std::move() when PropertyValue is fixed
new (&string_v) std::string(value.Value<std::string>());
break;
case PropertyValue::Type::List: {
// TODO: std::move() when PropertyValue is fixed
type_ = Type::List;
auto vec = value.Value<std::vector<PropertyValue>>();
new (&list_v)
std::vector<TypedValue>(std::make_move_iterator(vec.begin()),
std::make_move_iterator(vec.end()));
break;
}
case PropertyValue::Type::Map: {
// TODO: std::move() when PropertyValue is fixed
type_ = Type::Map;
auto map = value.Value<std::map<std::string, PropertyValue>>();
new (&map_v) std::map<std::string, TypedValue>(
std::make_move_iterator(map.begin()),
std::make_move_iterator(map.end()));
break;
}
}
value = PropertyValue::Null;
}
TypedValue::TypedValue(const TypedValue &other) : type_(other.type_) { TypedValue::TypedValue(const TypedValue &other) : type_(other.type_) {
switch (other.type_) { switch (other.type_) {
case TypedValue::Type::Null: case TypedValue::Type::Null:
@ -85,6 +131,42 @@ TypedValue::TypedValue(const TypedValue &other) : type_(other.type_) {
LOG(FATAL) << "Unsupported TypedValue::Type"; LOG(FATAL) << "Unsupported TypedValue::Type";
} }
TypedValue::TypedValue(TypedValue &&other) : type_(other.type_) {
switch (other.type_) {
case TypedValue::Type::Null:
break;
case TypedValue::Type::Bool:
this->bool_v = other.bool_v;
break;
case Type::Int:
this->int_v = other.int_v;
break;
case Type::Double:
this->double_v = other.double_v;
break;
case TypedValue::Type::String:
new (&string_v) std::string(std::move(other.string_v));
break;
case Type::List:
new (&list_v) std::vector<TypedValue>(std::move(other.list_v));
break;
case Type::Map:
new (&map_v) std::map<std::string, TypedValue>(std::move(other.map_v));
break;
case Type::Vertex:
new (&vertex_v) VertexAccessor(std::move(other.vertex_v));
break;
case Type::Edge:
new (&edge_v) EdgeAccessor(std::move(other.edge_v));
break;
case Type::Path:
new (&path_v) Path(std::move(other.path_v));
break;
}
other = TypedValue::Null;
}
TypedValue::operator PropertyValue() const { TypedValue::operator PropertyValue() const {
switch (type_) { switch (type_) {
case TypedValue::Type::Null: case TypedValue::Type::Null:
@ -227,35 +309,57 @@ std::ostream &operator<<(std::ostream &os, const TypedValue &value) {
LOG(FATAL) << "Unsupported PropertyValue::Type"; LOG(FATAL) << "Unsupported PropertyValue::Type";
} }
#define DEFINE_TYPED_VALUE_ASSIGNMENT(type_param, typed_value_type, member) \ #define DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(type_param, typed_value_type, \
TypedValue &TypedValue::operator=(const type_param &other) { \ member) \
if (this->type_ == TypedValue::Type::typed_value_type) { \ TypedValue &TypedValue::operator=(type_param other) { \
this->member = other; \ if (this->type_ == TypedValue::Type::typed_value_type) { \
} else { \ this->member = other; \
*this = TypedValue(other); \ } else { \
} \ *this = TypedValue(other); \
\ } \
return *this; \ \
return *this; \
} }
DEFINE_TYPED_VALUE_ASSIGNMENT(char *const, String, string_v) DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const char *, String, string_v)
DEFINE_TYPED_VALUE_ASSIGNMENT(int, Int, int_v) DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(int, Int, int_v)
DEFINE_TYPED_VALUE_ASSIGNMENT(bool, Bool, bool_v) DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(bool, Bool, bool_v)
DEFINE_TYPED_VALUE_ASSIGNMENT(int64_t, Int, int_v) DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(int64_t, Int, int_v)
DEFINE_TYPED_VALUE_ASSIGNMENT(double, Double, double_v) DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(double, Double, double_v)
DEFINE_TYPED_VALUE_ASSIGNMENT(std::string, String, string_v) DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const std::string &, String, string_v)
DEFINE_TYPED_VALUE_ASSIGNMENT(std::vector<TypedValue>, List, list_v) DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const std::vector<TypedValue> &, List,
DEFINE_TYPED_VALUE_ASSIGNMENT(TypedValue::value_map_t, Map, map_v) list_v)
DEFINE_TYPED_VALUE_ASSIGNMENT(VertexAccessor, Vertex, vertex_v) DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const TypedValue::value_map_t &, Map, map_v)
DEFINE_TYPED_VALUE_ASSIGNMENT(EdgeAccessor, Edge, edge_v) DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const VertexAccessor &, Vertex, vertex_v)
DEFINE_TYPED_VALUE_ASSIGNMENT(Path, Path, path_v) DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const EdgeAccessor &, Edge, edge_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const Path &, Path, path_v)
#undef DEFINE_TYPED_VALUE_ASSIGNMENT #undef DEFINE_TYPED_VALUE_COPY_ASSIGNMENT
#define DEFINE_TYPED_VALUE_MOVE_ASSIGNMENT(type_param, typed_value_type, \
member) \
TypedValue &TypedValue::operator=(type_param &&other) { \
if (this->type_ == TypedValue::Type::typed_value_type) { \
this->member = std::move(other); \
} else { \
*this = TypedValue(std::move(other)); \
} \
\
return *this; \
}
DEFINE_TYPED_VALUE_MOVE_ASSIGNMENT(std::string, String, string_v)
DEFINE_TYPED_VALUE_MOVE_ASSIGNMENT(std::vector<TypedValue>, List, list_v)
DEFINE_TYPED_VALUE_MOVE_ASSIGNMENT(TypedValue::value_map_t, Map, map_v)
DEFINE_TYPED_VALUE_MOVE_ASSIGNMENT(VertexAccessor, Vertex, vertex_v)
DEFINE_TYPED_VALUE_MOVE_ASSIGNMENT(EdgeAccessor, Edge, edge_v)
DEFINE_TYPED_VALUE_MOVE_ASSIGNMENT(Path, Path, path_v)
#undef DEFINE_TYPED_VALUE_MOVE_ASSIGNMENT
TypedValue &TypedValue::operator=(const TypedValue &other) { TypedValue &TypedValue::operator=(const TypedValue &other) {
if (this != &other) { if (this != &other) {
this->~TypedValue(); DestroyValue();
// set the type of this
type_ = other.type_; type_ = other.type_;
switch (other.type_) { switch (other.type_) {
@ -294,44 +398,92 @@ TypedValue &TypedValue::operator=(const TypedValue &other) {
return *this; return *this;
} }
TypedValue &TypedValue::operator=(TypedValue &&other) {
if (this != &other) {
DestroyValue();
type_ = other.type_;
switch (other.type_) {
case TypedValue::Type::Null:
break;
case TypedValue::Type::Bool:
this->bool_v = other.bool_v;
break;
case TypedValue::Type::Int:
this->int_v = other.int_v;
break;
case TypedValue::Type::Double:
this->double_v = other.double_v;
break;
case TypedValue::Type::String:
new (&string_v) std::string(std::move(other.string_v));
break;
case TypedValue::Type::List:
new (&list_v) std::vector<TypedValue>(std::move(other.list_v));
break;
case TypedValue::Type::Map:
new (&map_v) std::map<std::string, TypedValue>(std::move(other.map_v));
break;
case TypedValue::Type::Vertex:
new (&vertex_v) VertexAccessor(std::move(other.vertex_v));
break;
case TypedValue::Type::Edge:
new (&edge_v) EdgeAccessor(std::move(other.edge_v));
break;
case TypedValue::Type::Path:
new (&path_v) Path(std::move(other.path_v));
break;
}
other = TypedValue::Null;
}
return *this;
}
const TypedValue TypedValue::Null = TypedValue(); const TypedValue TypedValue::Null = TypedValue();
TypedValue::~TypedValue() { void TypedValue::DestroyValue() {
switch (type_) { switch (type_) {
// destructor for primitive types does nothing // destructor for primitive types does nothing
case Type::Null: case Type::Null:
case Type::Bool: case Type::Bool:
case Type::Int: case Type::Int:
case Type::Double: case Type::Double:
return; break;
// we need to call destructors for non primitive types since we used // we need to call destructors for non primitive types since we used
// placement new // placement new
case Type::String: case Type::String:
// Clang fails to compile ~std::string. It seems it is a bug in some // Clang fails to compile ~std::string. It seems it is a bug in some
// versions of clang. using namespace std statement solves the issue. // versions of clang. using namespace std statement solves the issue.
using namespace std; using namespace std;
string_v.~string(); string_v.~string();
return; break;
case Type::List: case Type::List:
using namespace std; using namespace std;
list_v.~vector<TypedValue>(); list_v.~vector<TypedValue>();
return; break;
case Type::Map: case Type::Map:
using namespace std; using namespace std;
map_v.~map<std::string, TypedValue>(); map_v.~map<std::string, TypedValue>();
return; break;
case Type::Vertex: case Type::Vertex:
vertex_v.~VertexAccessor(); vertex_v.~VertexAccessor();
return; break;
case Type::Edge: case Type::Edge:
edge_v.~EdgeAccessor(); edge_v.~EdgeAccessor();
return; break;
case Type::Path: case Type::Path:
path_v.~Path(); path_v.~Path();
return; break;
} }
LOG(FATAL) << "Unsupported TypedValue::Type";
type_ = TypedValue::Type::Null;
}
TypedValue::~TypedValue() {
DestroyValue();
} }
/** /**
@ -651,7 +803,7 @@ bool TypedValue::BoolEqual::operator()(const TypedValue &lhs,
default: default:
LOG(FATAL) LOG(FATAL)
<< "Equality between two TypedValues resulted in something other " << "Equality between two TypedValues resulted in something other "
"then Null or bool"; "than Null or bool";
} }
} }

View File

@ -6,6 +6,7 @@
#include <memory> #include <memory>
#include <string> #include <string>
#include <unordered_set> #include <unordered_set>
#include <utility>
#include <vector> #include <vector>
#include "query/path.hpp" #include "query/path.hpp"
@ -55,6 +56,7 @@ class TypedValue
* in the floating point domain (TypedValue operator== behaves the same). * in the floating point domain (TypedValue operator== behaves the same).
* */ * */
using unordered_set = std::unordered_set<TypedValue, Hash, BoolEqual>; using unordered_set = std::unordered_set<TypedValue, Hash, BoolEqual>;
using value_map_t = std::map<std::string, TypedValue>;
/** Private default constructor, makes Null */ /** Private default constructor, makes Null */
TypedValue() : type_(Type::Null) {} TypedValue() : type_(Type::Null) {}
@ -77,6 +79,9 @@ class TypedValue
// single static reference to Null, used whenever Null should be returned // single static reference to Null, used whenever Null should be returned
static const TypedValue Null; static const TypedValue Null;
TypedValue(const TypedValue &other);
TypedValue(TypedValue &&other);
// constructors for primitive types // constructors for primitive types
TypedValue(bool value) : type_(Type::Bool) { bool_v = value; } TypedValue(bool value) : type_(Type::Bool) { bool_v = value; }
TypedValue(int value) : type_(Type::Int) { int_v = value; } TypedValue(int value) : type_(Type::Int) { int_v = value; }
@ -86,7 +91,7 @@ class TypedValue
// conversion function to PropertyValue // conversion function to PropertyValue
explicit operator PropertyValue() const; explicit operator PropertyValue() const;
/// constructors for non-primitive types // copy constructors for non-primitive types
TypedValue(const std::string &value) : type_(Type::String) { TypedValue(const std::string &value) : type_(Type::String) {
new (&string_v) std::string(value); new (&string_v) std::string(value);
} }
@ -109,33 +114,50 @@ class TypedValue
TypedValue(const Path &path) : type_(Type::Path) { new (&path_v) Path(path); } TypedValue(const Path &path) : type_(Type::Path) { new (&path_v) Path(path); }
TypedValue(const PropertyValue &value); TypedValue(const PropertyValue &value);
/** // move constructors for non-primitive types
* There are all sorts of explicit assignments here because this way we avoid TypedValue(std::string &&value) : type_(Type::String) {
* destructor and constructor of TypedValue for creating intermediary values, new (&string_v) std::string(std::move(value));
* and can fill the typed value storage directly if it has the same underlying }
* type. TypedValue(std::vector<TypedValue> &&value) : type_(Type::List) {
*/ new (&list_v) std::vector<TypedValue>(std::move(value));
#define DECLARE_TYPED_VALUE_ASSIGNMENT(type_param) \ }
TypedValue &operator=(const type_param &other); TypedValue(std::map<std::string, TypedValue> &&value) : type_(Type::Map) {
new (&map_v) std::map<std::string, TypedValue>(std::move(value));
}
TypedValue(VertexAccessor &&vertex) : type_(Type::Vertex) {
new (&vertex_v) VertexAccessor(std::move(vertex));
}
TypedValue(EdgeAccessor &&edge) : type_(Type::Edge) {
new (&edge_v) EdgeAccessor(std::move(edge));
}
TypedValue(Path &&path) : type_(Type::Path) {
new (&path_v) Path(std::move(path));
}
TypedValue(PropertyValue &&value);
using value_map_t = std::map<std::string, TypedValue>; // copy assignment operators
// Don't delete char * const assignment because char* strings will be assigned TypedValue &operator=(const char *);
// using boolean assignment (not good). TypedValue &operator=(int);
DECLARE_TYPED_VALUE_ASSIGNMENT(char *const) TypedValue &operator=(bool);
DECLARE_TYPED_VALUE_ASSIGNMENT(int) TypedValue &operator=(int64_t);
DECLARE_TYPED_VALUE_ASSIGNMENT(bool) TypedValue &operator=(double);
DECLARE_TYPED_VALUE_ASSIGNMENT(int64_t) TypedValue &operator=(const std::string &);
DECLARE_TYPED_VALUE_ASSIGNMENT(double) TypedValue &operator=(const std::vector<TypedValue> &);
DECLARE_TYPED_VALUE_ASSIGNMENT(std::string) TypedValue &operator=(const TypedValue::value_map_t &);
DECLARE_TYPED_VALUE_ASSIGNMENT(std::vector<TypedValue>) TypedValue &operator=(const VertexAccessor &);
DECLARE_TYPED_VALUE_ASSIGNMENT(TypedValue::value_map_t) TypedValue &operator=(const EdgeAccessor &);
DECLARE_TYPED_VALUE_ASSIGNMENT(VertexAccessor) TypedValue &operator=(const Path &);
DECLARE_TYPED_VALUE_ASSIGNMENT(EdgeAccessor) TypedValue &operator=(const TypedValue &);
DECLARE_TYPED_VALUE_ASSIGNMENT(Path)
DECLARE_TYPED_VALUE_ASSIGNMENT(TypedValue) // move assignment operators
#undef DECLARE_TYPED_VALUE_ASSIGNMENT TypedValue &operator=(std::string &&);
TypedValue &operator=(std::vector<TypedValue> &&);
TypedValue &operator=(TypedValue::value_map_t &&);
TypedValue &operator=(VertexAccessor &&);
TypedValue &operator=(EdgeAccessor &&);
TypedValue &operator=(Path &&);
TypedValue &operator=(TypedValue &&);
TypedValue(const TypedValue &other);
~TypedValue(); ~TypedValue();
Type type() const { return type_; } Type type() const { return type_; }
@ -189,6 +211,8 @@ class TypedValue
friend std::ostream &operator<<(std::ostream &stream, const TypedValue &prop); friend std::ostream &operator<<(std::ostream &stream, const TypedValue &prop);
private: private:
void DestroyValue();
// storage for the value of the property // storage for the value of the property
union { union {
bool bool_v; bool bool_v;

View File

@ -10,8 +10,8 @@
#include "utils/total_ordering.hpp" #include "utils/total_ordering.hpp"
/** /**
* Encapsulation of a value and it's type encapsulated in a class that has no * Encapsulation of a value and its type in a class that has no compile-time
* compiled-time info about that type. * info about the type.
* *
* Values can be of a number of predefined types that are enumerated in * Values can be of a number of predefined types that are enumerated in
* PropertyValue::Type. Each such type corresponds to exactly one C++ type. * PropertyValue::Type. Each such type corresponds to exactly one C++ type.