Skip to content

Commit

Permalink
Make Obj trivial and add a separate ObjCollectionParent type
Browse files Browse the repository at this point in the history
Giving Obj a virtual base class turns out to have a meaningful performance
impact.
  • Loading branch information
tgoyne committed Mar 5, 2024
1 parent bd9606a commit bd61f4b
Show file tree
Hide file tree
Showing 19 changed files with 384 additions and 293 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
### Fixed
* <How do the end-user experience this issue? what was the impact?> ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?)
* Fix a data race in change notification delivery when running at debug log level ([PR #7402](https://github.com/realm/realm-core/pull/7402), since v14.0.0).
* Fix a 10-15% performance regression when reading data from the Realm resulting from Obj being made a non-trivial type ([PR #7402](https://github.com/realm/realm-core/pull/7402), since v14.0.0).

### Breaking changes
* None.
Expand Down
8 changes: 4 additions & 4 deletions src/realm/collection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ class CollectionBaseImpl : public Interface, protected ArrayParent {
using Interface::get_target_table;

protected:
Obj m_obj_mem;
ObjCollectionParent m_obj_mem;
std::shared_ptr<CollectionParent> m_col_parent;
CollectionParent::Index m_index;
ColKey m_col_key;
Expand Down Expand Up @@ -731,19 +731,19 @@ class CollectionBaseImpl : public Interface, protected ArrayParent {
void set_backlink(ColKey col_key, ObjLink new_link) const
{
check_parent();
m_parent->set_backlink(col_key, new_link);
m_parent->get_object().set_backlink(col_key, new_link);
}
// Used when replacing a link, return true if CascadeState contains objects to remove
bool replace_backlink(ColKey col_key, ObjLink old_link, ObjLink new_link, CascadeState& state) const
{
check_parent();
return m_parent->replace_backlink(col_key, old_link, new_link, state);
return m_parent->get_object().replace_backlink(col_key, old_link, new_link, state);
}
// Used when removing a backlink, return true if CascadeState contains objects to remove
bool remove_backlink(ColKey col_key, ObjLink old_link, CascadeState& state) const
{
check_parent();
return m_parent->remove_backlink(col_key, old_link, state);
return m_parent->get_object().remove_backlink(col_key, old_link, state);
}

/// Reset the accessor's tracking of the content version. Derived classes
Expand Down
239 changes: 58 additions & 181 deletions src/realm/collection_parent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,212 +79,81 @@ void CollectionParent::check_level() const
throw LogicError(ErrorCodes::LimitExceeded, "Max nesting level reached");
}
}
void CollectionParent::set_backlink(ColKey col_key, ObjLink new_link) const
{
if (new_link && new_link.get_obj_key()) {
auto t = get_table();
auto target_table = t->get_parent_group()->get_table(new_link.get_table_key());
ColKey backlink_col_key;
auto type = col_key.get_type();
if (type == col_type_TypedLink || type == col_type_Mixed || col_key.is_dictionary()) {
// This may modify the target table
backlink_col_key = target_table->find_or_add_backlink_column(col_key, t->get_key());
// it is possible that this was a link to the same table and that adding a backlink column has
// caused the need to update this object as well.
update_if_needed();
}
else {
backlink_col_key = t->get_opposite_column(col_key);
}
auto obj_key = new_link.get_obj_key();
auto target_obj = obj_key.is_unresolved() ? target_table->try_get_tombstone(obj_key)
: target_table->try_get_object(obj_key);
if (!target_obj) {
throw InvalidArgument(ErrorCodes::KeyNotFound, "Target object not found");
}
target_obj.add_backlink(backlink_col_key, get_object().get_key());
}
}

bool CollectionParent::replace_backlink(ColKey col_key, ObjLink old_link, ObjLink new_link, CascadeState& state) const
template <typename Base, template <typename> typename Collection, typename LinkCol>
std::unique_ptr<Base> create_collection(ColKey col_key, size_t level)
{
bool recurse = remove_backlink(col_key, old_link, state);
set_backlink(col_key, new_link);

return recurse;
}

bool CollectionParent::remove_backlink(ColKey col_key, ObjLink old_link, CascadeState& state) const
{
if (old_link && old_link.get_obj_key()) {
auto t = get_table();
REALM_ASSERT(t->valid_column(col_key));
ObjKey old_key = old_link.get_obj_key();
auto target_obj = t->get_parent_group()->get_object(old_link);
TableRef target_table = target_obj.get_table();
ColKey backlink_col_key;
auto type = col_key.get_type();
if (type == col_type_TypedLink || type == col_type_Mixed || col_key.is_dictionary()) {
backlink_col_key = target_table->find_or_add_backlink_column(col_key, t->get_key());
}
else {
backlink_col_key = t->get_opposite_column(col_key);
}

bool strong_links = target_table->is_embedded();
bool is_unres = old_key.is_unresolved();

bool last_removed = target_obj.remove_one_backlink(backlink_col_key, get_object().get_key()); // Throws
if (is_unres) {
if (last_removed) {
// Check is there are more backlinks
if (!target_obj.has_backlinks(false)) {
// Tombstones can be erased right away - there is no cascading effect
target_table->m_tombstones->erase(old_key, state);
}
}
}
else {
return state.enqueue_for_cascade(target_obj, strong_links, last_removed);
}
}

return false;
}

LstBasePtr CollectionParent::get_listbase_ptr(ColKey col_key) const
{
auto table = get_table();
auto attr = table->get_column_attr(col_key);
REALM_ASSERT(attr.test(col_attr_List) || attr.test(col_attr_Nullable));
bool nullable = attr.test(col_attr_Nullable);

switch (table->get_column_type(col_key)) {
case type_Int: {
bool nullable = col_key.get_attrs().test(col_attr_Nullable);
switch (col_key.get_type()) {
case col_type_Int:
if (nullable)
return std::make_unique<Lst<util::Optional<Int>>>(col_key);
else
return std::make_unique<Lst<Int>>(col_key);
}
case type_Bool: {
return std::make_unique<Collection<util::Optional<Int>>>(col_key);
return std::make_unique<Collection<Int>>(col_key);
case col_type_Bool:
if (nullable)
return std::make_unique<Lst<util::Optional<Bool>>>(col_key);
else
return std::make_unique<Lst<Bool>>(col_key);
}
case type_Float: {
return std::make_unique<Collection<util::Optional<Bool>>>(col_key);
return std::make_unique<Collection<Bool>>(col_key);
case col_type_Float:
if (nullable)
return std::make_unique<Lst<util::Optional<Float>>>(col_key);
else
return std::make_unique<Lst<Float>>(col_key);
}
case type_Double: {
return std::make_unique<Collection<util::Optional<Float>>>(col_key);
return std::make_unique<Collection<Float>>(col_key);
case col_type_Double:
if (nullable)
return std::make_unique<Lst<util::Optional<Double>>>(col_key);
else
return std::make_unique<Lst<Double>>(col_key);
}
case type_String: {
return std::make_unique<Lst<String>>(col_key);
}
case type_Binary: {
return std::make_unique<Lst<Binary>>(col_key);
}
case type_Timestamp: {
return std::make_unique<Lst<Timestamp>>(col_key);
}
case type_Decimal: {
return std::make_unique<Lst<Decimal128>>(col_key);
}
case type_ObjectId: {
return std::make_unique<Collection<util::Optional<Double>>>(col_key);
return std::make_unique<Collection<Double>>(col_key);
case col_type_String:
return std::make_unique<Collection<String>>(col_key);
case col_type_Binary:
return std::make_unique<Collection<Binary>>(col_key);
case col_type_Timestamp:
return std::make_unique<Collection<Timestamp>>(col_key);
case col_type_Decimal:
return std::make_unique<Collection<Decimal128>>(col_key);
case col_type_ObjectId:
if (nullable)
return std::make_unique<Lst<util::Optional<ObjectId>>>(col_key);
else
return std::make_unique<Lst<ObjectId>>(col_key);
}
case type_UUID: {
return std::make_unique<Collection<util::Optional<ObjectId>>>(col_key);
return std::make_unique<Collection<ObjectId>>(col_key);
case col_type_UUID:
if (nullable)
return std::make_unique<Lst<util::Optional<UUID>>>(col_key);
else
return std::make_unique<Lst<UUID>>(col_key);
}
case type_TypedLink: {
return std::make_unique<Lst<ObjLink>>(col_key);
}
case type_Mixed: {
return std::make_unique<Lst<Mixed>>(col_key, get_level() + 1);
}
case type_Link:
return std::make_unique<LnkLst>(col_key);
return std::make_unique<Collection<util::Optional<UUID>>>(col_key);
return std::make_unique<Collection<UUID>>(col_key);
case col_type_TypedLink:
return std::make_unique<Collection<ObjLink>>(col_key);
case col_type_Mixed:
return std::make_unique<Collection<Mixed>>(col_key, level + 1);
case col_type_Link:
return std::make_unique<LinkCol>(col_key);
default:
REALM_TERMINATE("Unsupported column type.");
}
REALM_TERMINATE("Unsupported column type");
}

SetBasePtr CollectionParent::get_setbase_ptr(ColKey col_key) const
LstBasePtr CollectionParent::get_listbase_ptr(ColKey col_key, size_t level)
{
auto table = get_table();
auto attr = table->get_column_attr(col_key);
REALM_ASSERT(attr.test(col_attr_Set));
bool nullable = attr.test(col_attr_Nullable);
REALM_ASSERT(col_key.get_attrs().test(col_attr_List) || col_key.get_type() == col_type_Mixed);
return create_collection<LstBase, Lst, LnkLst>(col_key, level);
}

switch (table->get_column_type(col_key)) {
case type_Int:
if (nullable)
return std::make_unique<Set<util::Optional<Int>>>(col_key);
return std::make_unique<Set<Int>>(col_key);
case type_Bool:
if (nullable)
return std::make_unique<Set<util::Optional<Bool>>>(col_key);
return std::make_unique<Set<Bool>>(col_key);
case type_Float:
if (nullable)
return std::make_unique<Set<util::Optional<Float>>>(col_key);
return std::make_unique<Set<Float>>(col_key);
case type_Double:
if (nullable)
return std::make_unique<Set<util::Optional<Double>>>(col_key);
return std::make_unique<Set<Double>>(col_key);
case type_String:
return std::make_unique<Set<String>>(col_key);
case type_Binary:
return std::make_unique<Set<Binary>>(col_key);
case type_Timestamp:
return std::make_unique<Set<Timestamp>>(col_key);
case type_Decimal:
return std::make_unique<Set<Decimal128>>(col_key);
case type_ObjectId:
if (nullable)
return std::make_unique<Set<util::Optional<ObjectId>>>(col_key);
return std::make_unique<Set<ObjectId>>(col_key);
case type_UUID:
if (nullable)
return std::make_unique<Set<util::Optional<UUID>>>(col_key);
return std::make_unique<Set<UUID>>(col_key);
case type_TypedLink:
return std::make_unique<Set<ObjLink>>(col_key);
case type_Mixed:
return std::make_unique<Set<Mixed>>(col_key);
case type_Link:
return std::make_unique<LnkSet>(col_key);
}
REALM_TERMINATE("Unsupported column type.");
SetBasePtr CollectionParent::get_setbase_ptr(ColKey col_key, size_t level)
{
REALM_ASSERT(col_key.get_attrs().test(col_attr_Set));
return create_collection<SetBase, Set, LnkSet>(col_key, level);
}

CollectionBasePtr CollectionParent::get_collection_ptr(ColKey col_key) const
CollectionBasePtr CollectionParent::get_collection_ptr(ColKey col_key, size_t level)
{
if (col_key.is_list()) {
return get_listbase_ptr(col_key);
return get_listbase_ptr(col_key, level);
}
else if (col_key.is_set()) {
return get_setbase_ptr(col_key);
return get_setbase_ptr(col_key, level);
}
else if (col_key.is_dictionary()) {
return std::make_unique<Dictionary>(col_key, get_level() + 1);
return std::make_unique<Dictionary>(col_key, level + 1);
}
return {};
}


int64_t CollectionParent::generate_key(size_t sz)
{
static std::mt19937 gen32;
Expand All @@ -307,5 +176,13 @@ int64_t CollectionParent::generate_key(size_t sz)
return key;
}

void CollectionParent::set_key(BPlusTreeMixed& tree, size_t index)
{
int64_t key = generate_key(tree.size());
while (tree.find_key(key) != realm::not_found) {
key++;
}
tree.set_key(index, key);
}

} // namespace realm
26 changes: 11 additions & 15 deletions src/realm/collection_parent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@
#define REALM_COLLECTION_PARENT_HPP

#include <realm/alloc.hpp>
#include <realm/table_ref.hpp>
#include <realm/path.hpp>
#include <realm/mixed.hpp>
#include <realm/path.hpp>
#include <realm/table_ref.hpp>

namespace realm {

class Obj;
class Replication;
class CascadeState;
class BPlusTreeMixed;

class Collection;
class CollectionBase;
Expand Down Expand Up @@ -95,6 +96,13 @@ class CollectionParent : public std::enable_shared_from_this<CollectionParent> {
/// Get table of owning object
virtual TableRef get_table() const noexcept = 0;

static LstBasePtr get_listbase_ptr(ColKey col_key, size_t level);
static SetBasePtr get_setbase_ptr(ColKey col_key, size_t level);
static CollectionBasePtr get_collection_ptr(ColKey col_key, size_t level);

static int64_t generate_key(size_t sz);
static void set_key(BPlusTreeMixed& tree, size_t index);

protected:
friend class Collection;
template <class>
Expand Down Expand Up @@ -129,20 +137,8 @@ class CollectionParent : public std::enable_shared_from_this<CollectionParent> {
/// Set the top ref in parent
virtual void set_collection_ref(Index, ref_type ref, CollectionType) = 0;

/// Get the counter which is incremented whenever the root Obj is updated.
virtual uint32_t parent_version() const noexcept = 0;

// Used when inserting a new link. You will not remove existing links in this process
void set_backlink(ColKey col_key, ObjLink new_link) const;
// Used when replacing a link, return true if CascadeState contains objects to remove
bool replace_backlink(ColKey col_key, ObjLink old_link, ObjLink new_link, CascadeState& state) const;
// Used when removing a backlink, return true if CascadeState contains objects to remove
bool remove_backlink(ColKey col_key, ObjLink old_link, CascadeState& state) const;

LstBasePtr get_listbase_ptr(ColKey col_key) const;
SetBasePtr get_setbase_ptr(ColKey col_key) const;
CollectionBasePtr get_collection_ptr(ColKey col_key) const;

static int64_t generate_key(size_t sz);
};

} // namespace realm
Expand Down
7 changes: 1 addition & 6 deletions src/realm/dictionary.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -441,11 +441,7 @@ void Dictionary::insert_collection(const PathElement& path_elem, CollectionType
if (!old_val || *old_val != new_val) {
m_values->ensure_keys();
auto [it, inserted] = insert(path_elem.get_key(), new_val);
int64_t key = generate_key(size());
while (m_values->find_key(key) != realm::not_found) {
key++;
}
m_values->set_key(it.index(), key);
set_key(*m_values, it.index());
}
}

Expand Down Expand Up @@ -686,7 +682,6 @@ void Dictionary::ensure_created()
{
constexpr bool allow_create = true;
if (do_update_if_needed(allow_create) == UpdateStatus::Detached) {
// FIXME: untested
throw StaleAccessor("Dictionary no longer exists");
}
}
Expand Down
Loading

0 comments on commit bd61f4b

Please sign in to comment.