Use Allocator for map in TypedValue

Reviewers: mtomic, llugovic, mferencevic

Reviewed By: llugovic

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D2116
This commit is contained in:
Teon Banek 2019-06-03 16:54:20 +02:00
parent 0b2240e6e4
commit 96505b7fb4
8 changed files with 138 additions and 47 deletions

View File

@ -43,7 +43,7 @@ void Save(const query::TypedValue &value, slk::Builder *builder,
size_t size = map.size();
slk::Save(size, builder);
for (const auto &kv : map) {
slk::Save(kv.first, builder);
slk::Save(std::string(kv.first), builder);
slk::Save(kv.second, builder, versions, worker_id);
}
return;

View File

@ -184,7 +184,7 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
throw QueryRuntimeException("Expected a string as a map index, got {}.",
index.type());
const auto &map = lhs.ValueMap();
auto found = map.find(std::string(index.ValueString()));
auto found = map.find(index.ValueString());
if (found == map.end()) return TypedValue::Null;
return found->second;
}
@ -276,8 +276,8 @@ class ExpressionEvaluator : public ExpressionVisitor<TypedValue> {
return expression_result.Value<EdgeAccessor>().PropsAt(
GetProperty(property_lookup.property_));
case TypedValue::Type::Map: {
auto &map = expression_result.ValueMap();
auto found = map.find(property_lookup.property_.name);
const auto &map = expression_result.ValueMap();
auto found = map.find(property_lookup.property_.name.c_str());
if (found == map.end()) return TypedValue::Null;
return found->second;
}

View File

@ -2035,7 +2035,8 @@ void SetProperties::SetPropertiesCursor::Set(TRecordAccessor &record,
break;
case TypedValue::Type::Map: {
for (const auto &kv : rhs.ValueMap())
PropsSetChecked(&record, db_.Property(kv.first), kv.second);
PropsSetChecked(&record, db_.Property(std::string(kv.first)),
kv.second);
break;
}
default:

View File

@ -52,8 +52,9 @@ TypedValue::TypedValue(const PropertyValue &value,
}
case PropertyValue::Type::Map: {
type_ = Type::Map;
auto map = value.Value<std::map<std::string, PropertyValue>>();
new (&map_v) std::map<std::string, TypedValue>(map.begin(), map.end());
const auto &map = value.Value<std::map<std::string, PropertyValue>>();
new (&map_v) TMap(memory_);
for (const auto &kv : map) map_v.emplace(kv.first, kv.second);
return;
}
}
@ -98,9 +99,8 @@ TypedValue::TypedValue(PropertyValue &&other, utils::MemoryResource *memory)
case PropertyValue::Type::Map: {
type_ = Type::Map;
auto &map = other.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()));
new (&map_v) TMap(memory_);
for (auto &kv : map) map_v.emplace(kv.first, std::move(kv.second));
break;
}
}
@ -134,7 +134,7 @@ TypedValue::TypedValue(const TypedValue &other, utils::MemoryResource *memory)
new (&list_v) TVector(other.list_v, memory_);
return;
case Type::Map:
new (&map_v) std::map<std::string, TypedValue>(other.map_v);
new (&map_v) TMap(other.map_v, memory_);
return;
case Type::Vertex:
new (&vertex_v) VertexAccessor(other.vertex_v);
@ -173,7 +173,7 @@ TypedValue::TypedValue(TypedValue &&other, utils::MemoryResource *memory)
new (&list_v) TVector(std::move(other.list_v), memory_);
break;
case Type::Map:
new (&map_v) std::map<std::string, TypedValue>(std::move(other.map_v));
new (&map_v) TMap(std::move(other.map_v), memory_);
break;
case Type::Vertex:
new (&vertex_v) VertexAccessor(std::move(other.vertex_v));
@ -203,9 +203,11 @@ TypedValue::operator PropertyValue() const {
case TypedValue::Type::List:
return PropertyValue(
std::vector<PropertyValue>(list_v.begin(), list_v.end()));
case TypedValue::Type::Map:
return PropertyValue(
std::map<std::string, PropertyValue>(map_v.begin(), map_v.end()));
case TypedValue::Type::Map: {
std::map<std::string, PropertyValue> map;
for (const auto &kv : map_v) map.emplace(kv.first, kv.second);
return PropertyValue(std::move(map));
}
default:
break;
}
@ -245,7 +247,7 @@ DEFINE_VALUE_AND_TYPE_GETTERS(int64_t, Int, int_v)
DEFINE_VALUE_AND_TYPE_GETTERS(double, Double, double_v)
DEFINE_VALUE_AND_TYPE_GETTERS(TypedValue::TString, String, string_v)
DEFINE_VALUE_AND_TYPE_GETTERS(TypedValue::TVector, List, list_v)
DEFINE_VALUE_AND_TYPE_GETTERS(TypedValue::value_map_t, Map, map_v)
DEFINE_VALUE_AND_TYPE_GETTERS(TypedValue::TMap, Map, map_v)
DEFINE_VALUE_AND_TYPE_GETTERS(VertexAccessor, Vertex, vertex_v)
DEFINE_VALUE_AND_TYPE_GETTERS(EdgeAccessor, Edge, edge_v)
DEFINE_VALUE_AND_TYPE_GETTERS(Path, Path, path_v)
@ -363,7 +365,14 @@ TypedValue &TypedValue::operator=(const std::vector<TypedValue> &other) {
return *this;
}
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const TypedValue::value_map_t &, Map, map_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const TypedValue::TMap &, Map, map_v)
TypedValue &TypedValue::operator=(
const std::map<std::string, TypedValue> &other) {
*this = TypedValue(other, memory_);
return *this;
}
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const VertexAccessor &, Vertex, vertex_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const EdgeAccessor &, Edge, edge_v)
DEFINE_TYPED_VALUE_COPY_ASSIGNMENT(const Path &, Path, path_v)
@ -395,7 +404,13 @@ TypedValue &TypedValue::operator=(std::vector<TypedValue> &&other) {
return *this;
}
DEFINE_TYPED_VALUE_MOVE_ASSIGNMENT(TypedValue::value_map_t, Map, map_v)
DEFINE_TYPED_VALUE_MOVE_ASSIGNMENT(TypedValue::TMap, Map, map_v)
TypedValue &TypedValue::operator=(std::map<std::string, TypedValue> &&other) {
*this = TypedValue(std::move(other), memory_);
return *this;
}
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)
@ -433,7 +448,7 @@ TypedValue &TypedValue::operator=(const TypedValue &other) {
new (&list_v) TVector(other.list_v, memory_);
return *this;
case TypedValue::Type::Map:
new (&map_v) std::map<std::string, TypedValue>(other.map_v);
new (&map_v) TMap(other.map_v, memory_);
return *this;
case TypedValue::Type::Vertex:
new (&vertex_v) VertexAccessor(other.vertex_v);
@ -481,7 +496,7 @@ TypedValue &TypedValue::operator=(TypedValue &&other) noexcept(false) {
new (&list_v) TVector(std::move(other.list_v), memory_);
break;
case TypedValue::Type::Map:
new (&map_v) std::map<std::string, TypedValue>(std::move(other.map_v));
new (&map_v) TMap(std::move(other.map_v), memory_);
break;
case TypedValue::Type::Vertex:
new (&vertex_v) VertexAccessor(std::move(other.vertex_v));
@ -518,8 +533,7 @@ void TypedValue::DestroyValue() {
list_v.~TVector();
break;
case Type::Map:
using namespace std;
map_v.~map<std::string, TypedValue>();
map_v.~TMap();
break;
case Type::Vertex:
vertex_v.~VertexAccessor();
@ -904,7 +918,7 @@ size_t TypedValue::Hash::operator()(const TypedValue &value) const {
case TypedValue::Type::Map: {
size_t hash = 6543457;
for (const auto &kv : value.ValueMap()) {
hash ^= std::hash<std::string>{}(kv.first);
hash ^= std::hash<std::string_view>{}(kv.first);
hash ^= this->operator()(kv.second);
}
return hash;

View File

@ -6,7 +6,6 @@
#include <memory>
#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
#include <vector>
@ -72,13 +71,17 @@ class TypedValue {
using TString =
std::basic_string<char, std::char_traits<char>, utils::Allocator<char>>;
/**
* Unordered set of TypedValue items. Can contain at most one Null element,
* and treats an integral and floating point value as same if they are equal
* in the floating point domain (TypedValue operator== behaves the same).
*/
using unordered_set = std::unordered_set<TypedValue, Hash, BoolEqual>;
using value_map_t = std::map<std::string, TypedValue>;
// TypedValue at this exact moment of compilation is an incomplete type, and
// the standard says that instantiating a container with an incomplete type
// invokes undefined behaviour. The libstdc++-8.3.0 we are using supports
// std::map with incomplete type, but this is still murky territory. Note that
// since C++17, std::vector is explicitly said to support incomplete types.
// Use transparent std::less<void> which forwards to `operator<`, so that it's
// possible to use `find` with C-style (null terminated) strings without
// actually constructing (and allocating) a key.
using TMap = std::map<TString, TypedValue, std::less<void>,
utils::Allocator<std::pair<const TString, TypedValue>>>;
using TVector = std::vector<TypedValue, utils::Allocator<TypedValue>>;
/** Allocator type so that STL containers are aware that we need one */
@ -220,10 +223,32 @@ class TypedValue {
new (&list_v) TVector(value, memory_);
}
/** Construct a copy using the given utils::MemoryResource */
TypedValue(const std::map<std::string, TypedValue> &value,
utils::MemoryResource *memory = utils::NewDeleteResource())
: memory_(memory), type_(Type::Map) {
new (&map_v) std::map<std::string, TypedValue>(value);
new (&map_v) TMap(memory_);
for (const auto &kv : value) map_v.emplace(kv.first, kv.second);
}
/**
* Construct a copy of other.
* utils::MemoryResource is obtained by calling
* std::allocator_traits<>::
* select_on_container_copy_construction(other.get_allocator()).
* Since we use utils::Allocator, which does not propagate, this means that
* memory_ will be the default utils::NewDeleteResource().
*/
TypedValue(const TMap &other)
: TypedValue(other, std::allocator_traits<utils::Allocator<TypedValue>>::
select_on_container_copy_construction(
other.get_allocator())
.GetMemoryResource()) {}
/** Construct a copy using the given utils::MemoryResource */
TypedValue(const TMap &value, utils::MemoryResource *memory)
: memory_(memory), type_(Type::Map) {
new (&map_v) TMap(value, memory_);
}
TypedValue(const VertexAccessor &vertex,
@ -279,7 +304,7 @@ class TypedValue {
/**
* Perform an element-wise move of the other and use the given MemoryResource.
* Other will be not be empty, though elements may be Null.
* Other will be not be left empty, though elements may be Null.
*/
TypedValue(std::vector<TypedValue> &&other, utils::MemoryResource *memory)
: memory_(memory), type_(Type::List) {
@ -314,15 +339,44 @@ class TypedValue {
new (&list_v) TVector(std::move(other), memory_);
}
TypedValue(std::map<std::string, TypedValue> &&value) noexcept
: type_(Type::Map) {
new (&map_v) std::map<std::string, TypedValue>(std::move(value));
/**
* Perform an element-wise move using default utils::NewDeleteResource().
* Other will not be left empty, i.e. keys will exist but their values may
* be Null.
*/
TypedValue(std::map<std::string, TypedValue> &&other)
: TypedValue(std::move(other), utils::NewDeleteResource()) {}
/**
* Perform an element-wise move using the given MemoryResource.
* Other will not be left empty, i.e. keys will exist but their values may
* be Null.
*/
TypedValue(std::map<std::string, TypedValue> &&other,
utils::MemoryResource *memory)
: memory_(memory), type_(Type::Map) {
new (&map_v) TMap(memory_);
for (auto &kv : other) map_v.emplace(kv.first, std::move(kv.second));
}
TypedValue(std::map<std::string, TypedValue> &&value,
utils::MemoryResource *memory) noexcept
/**
* Construct with the value of other.
* utils::MemoryResource is obtained from other. After the move, other will be
* left empty.
*/
TypedValue(TMap &&other) noexcept
: TypedValue(std::move(other),
other.get_allocator().GetMemoryResource()) {}
/**
* Construct with the value of other and use the given MemoryResource.
* If `other.get_allocator() != *memory`, this call will perform an
* element-wise move and other is not guaranteed to be empty, i.e. keys may
* exist but their values may be Null.
*/
TypedValue(TMap &&other, utils::MemoryResource *memory)
: memory_(memory), type_(Type::Map) {
new (&map_v) std::map<std::string, TypedValue>(std::move(value));
new (&map_v) TMap(std::move(other), memory_);
}
TypedValue(VertexAccessor &&vertex) noexcept : type_(Type::Vertex) {
@ -377,7 +431,8 @@ class TypedValue {
TypedValue &operator=(const std::string_view &);
TypedValue &operator=(const TVector &);
TypedValue &operator=(const std::vector<TypedValue> &);
TypedValue &operator=(const TypedValue::value_map_t &);
TypedValue &operator=(const TMap &);
TypedValue &operator=(const std::map<std::string, TypedValue> &);
TypedValue &operator=(const VertexAccessor &);
TypedValue &operator=(const EdgeAccessor &);
TypedValue &operator=(const Path &);
@ -389,7 +444,8 @@ class TypedValue {
TypedValue &operator=(TString &&);
TypedValue &operator=(TVector &&);
TypedValue &operator=(std::vector<TypedValue> &&);
TypedValue &operator=(TypedValue::value_map_t &&);
TypedValue &operator=(TMap &&);
TypedValue &operator=(std::map<std::string, TypedValue> &&);
TypedValue &operator=(VertexAccessor &&);
TypedValue &operator=(EdgeAccessor &&);
TypedValue &operator=(Path &&);
@ -440,7 +496,7 @@ class TypedValue {
/** Check if the stored value is a list value */
bool IsList() const;
DECLARE_VALUE_AND_TYPE_GETTERS(value_map_t, Map)
DECLARE_VALUE_AND_TYPE_GETTERS(TMap, Map)
DECLARE_VALUE_AND_TYPE_GETTERS(VertexAccessor, Vertex)
DECLARE_VALUE_AND_TYPE_GETTERS(EdgeAccessor, Edge)
DECLARE_VALUE_AND_TYPE_GETTERS(Path, Path)
@ -478,9 +534,7 @@ class TypedValue {
// because of data locality.
TString string_v;
TVector list_v;
// clang doesn't allow unordered_map to have incomplete type as value so we
// we use map.
std::map<std::string, TypedValue> map_v;
TMap map_v;
VertexAccessor vertex_v;
EdgeAccessor edge_v;
Path path_v;

View File

@ -1009,7 +1009,7 @@ TEST_F(FunctionTest, Properties) {
auto prop_values_to_int = [](TypedValue t) {
std::unordered_map<std::string, int> properties;
for (auto property : t.ValueMap()) {
properties[property.first] = property.second.ValueInt();
properties[std::string(property.first)] = property.second.ValueInt();
}
return properties;
};

View File

@ -327,7 +327,8 @@ TEST(QueryPlan, AggregateGroupByValues) {
auto context = MakeContext(storage, symbol_table, &dba);
auto results = CollectProduce(*produce, &context);
ASSERT_EQ(results.size(), group_by_vals.size() - 2);
TypedValue::unordered_set result_group_bys;
std::unordered_set<TypedValue, TypedValue::Hash, TypedValue::BoolEqual>
result_group_bys;
for (const auto &row : results) {
ASSERT_EQ(2, row.size());
result_group_bys.insert(row[1]);

View File

@ -490,6 +490,27 @@ TEST_F(AllTypesFixture, PropagationOfMemoryOnConstruction) {
EXPECT_TRUE(TypedValue::BoolEqual{}(original[i], moved[i]));
EXPECT_TRUE(TypedValue::BoolEqual{}(original[i], copied[i]));
}
} else if (value.type() == TypedValue::Type::Map) {
ASSERT_EQ(move_constructed_value.type(), value.type());
const auto &original = value.ValueMap();
const auto &moved = move_constructed_value.ValueMap();
const auto &copied = copy_constructed_value.ValueMap();
auto expect_allocator = [](const auto &kv, auto *memory_resource) {
EXPECT_EQ(*kv.first.get_allocator().GetMemoryResource(),
*memory_resource);
EXPECT_EQ(*kv.second.GetMemoryResource(), *memory_resource);
};
for (const auto &kv : original) {
expect_allocator(kv, utils::NewDeleteResource());
auto moved_it = moved.find(kv.first);
ASSERT_NE(moved_it, moved.end());
expect_allocator(*moved_it, &monotonic_memory);
auto copied_it = copied.find(kv.first);
ASSERT_NE(copied_it, copied.end());
expect_allocator(*copied_it, &monotonic_memory);
EXPECT_TRUE(TypedValue::BoolEqual{}(kv.second, moved_it->second));
EXPECT_TRUE(TypedValue::BoolEqual{}(kv.second, copied_it->second));
}
}
}
}