mirror of
https://github.com/nlohmann/json.git
synced 2026-10-10 16:37:14 +00:00
Replace a container by a scalar in constant time once it is linked
Replacing an array or object that spans several nodes by a scalar looks up its parent from the root (find_parent), which is linear in the size of the document: setting every element of a 20,000 element array took more than half a second. The parent only has to switch to links once; afterward the extent of the replaced value no longer matters. Nodes that an entry of a moved sequence links to are now marked (node_flags::linked), and assign() skips the lookup for them. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
5 files changed
+62
-8
No files matched your search
@@ -348,9 +348,10 @@ class editor
|
||||
/// turn a null into an empty array/object in place
|
||||
static void become_empty(node* n, value_t k) noexcept
|
||||
{
|
||||
const std::uint8_t linked = n->flags & node_flags::linked;
|
||||
*n = node{};
|
||||
n->kind = static_cast<std::uint8_t>(k);
|
||||
n->flags = node_flags::is_new;
|
||||
n->flags = static_cast<std::uint8_t>(node_flags::is_new | linked);
|
||||
n->next = 1;
|
||||
}
|
||||
|
||||
@@ -358,13 +359,17 @@ class editor
|
||||
/// include slot (if known).
|
||||
void assign(node* slot, const encoded& e, node* parent, bool parent_known)
|
||||
{
|
||||
// an entry of a moved sequence links to the slot: it can take any extent
|
||||
const std::uint8_t linked = slot->flags & node_flags::linked;
|
||||
if (e.region == nullptr)
|
||||
{
|
||||
if (is_container(*slot) && slot->next > 1 && slot != m_doc.tape)
|
||||
if (is_container(*slot) && slot->next > 1 && slot != m_doc.tape && linked == 0)
|
||||
{
|
||||
// The slot spans its old elements in the enclosing sequence, but
|
||||
// a scalar is one node: the enclosing container first switches to
|
||||
// links (then the extent of the slot no longer matters).
|
||||
// links (then the extent of the slot no longer matters). Looking
|
||||
// for the container is linear in the size of the document, so
|
||||
// links (which are marked in the slot) avoid it.
|
||||
node* const p = parent_known ? parent : find_parent(m_doc, slot);
|
||||
if (p != nullptr && ((p->flags & node_flags::moved) == 0 || moved_capacity(m_doc, p) == 0))
|
||||
{
|
||||
@@ -372,6 +377,7 @@ class editor
|
||||
}
|
||||
}
|
||||
*slot = e.scalar;
|
||||
slot->flags = static_cast<std::uint8_t>(slot->flags | linked);
|
||||
return;
|
||||
}
|
||||
// an array/object: the slot keeps its extent (so that the enclosing
|
||||
@@ -394,7 +400,7 @@ class editor
|
||||
slot->len = r->len;
|
||||
slot->next = extent;
|
||||
// (set_moved() adds the moved flag to a slot that does not have it yet)
|
||||
slot->flags = was_moved ? static_cast<std::uint8_t>(node_flags::moved | node_flags::is_new) : std::uint8_t{0};
|
||||
slot->flags = static_cast<std::uint8_t>((was_moved ? node_flags::moved | node_flags::is_new : 0) | linked);
|
||||
set_moved(m_doc, slot, e.region, 0);
|
||||
edit_state_of(m_doc).regions[e.region] = slot;
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ inline node* block_of(document_data& d, node* n, std::size_t extra)
|
||||
set_moved(d, n, nh, cap);
|
||||
return nh;
|
||||
}
|
||||
reserve_moved(d); // (so that set_moved() below cannot throw)
|
||||
reserve_moved(d); // (so that set_moved() below cannot throw: the links are marked before)
|
||||
const bool object = n->kind == static_cast<std::uint8_t>(value_t::object);
|
||||
const std::size_t used = 1 + (static_cast<std::size_t>(n->len) * (object ? 2 : 1));
|
||||
const std::size_t cap = used + extra;
|
||||
@@ -185,7 +185,7 @@ inline node* block_of(document_data& d, node* n, std::size_t extra)
|
||||
{
|
||||
*o++ = *c++; // the key
|
||||
}
|
||||
make_link(*o, document_data::deref(c));
|
||||
make_link(*o, const_cast<node*>(document_data::deref(c))); // NOLINT(cppcoreguidelines-pro-type-const-cast): the nodes belong to the document
|
||||
++o;
|
||||
c = document_data::after(c);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ struct node_flags
|
||||
static constexpr std::uint8_t is_true = 4; ///< boolean value
|
||||
static constexpr std::uint8_t moved = 8; ///< array/object: the elements live in a separate sequence (editable documents)
|
||||
static constexpr std::uint8_t is_new = 16; ///< written by an edit: no source position
|
||||
static constexpr std::uint8_t linked = 32; ///< an entry of a moved sequence links to this value (editable documents): its extent in the parsed layout no longer matters
|
||||
};
|
||||
|
||||
/// kind of an entry of an edited sequence that stands for a value stored
|
||||
@@ -72,8 +73,10 @@ NLOHMANN_VIEW_ALWAYS_INLINE const node* link_target(const node& n) noexcept
|
||||
return t;
|
||||
}
|
||||
|
||||
inline void make_link(node& n, const node* target) noexcept
|
||||
/// let the entry n stand for the value at target (and mark the value)
|
||||
inline void make_link(node& n, node* target) noexcept
|
||||
{
|
||||
target->flags = static_cast<std::uint8_t>(target->flags | node_flags::linked);
|
||||
n = node{};
|
||||
n.kind = kind_link;
|
||||
std::memcpy(reinterpret_cast<unsigned char*>(&n) + 8, static_cast<const void*>(&target), sizeof(const node*)); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
|
||||
Reference in new issue
Block a user