Resume patch operations from the common prefix in constant time per step
patch() and patch_inplace() keep the values along the previously resolved pointer, so that the next operation resumes from the prefix the two paths have in common. To find the resume point, each cached step was tested from the deepest upwards with a memcmp over its whole prefix. If two paths shared a prefix and the previous path continued for D more tokens, the search compared D prefixes of that length: for two "test" operations whose paths share n tokens and continue for n more, the time was quadratic in n (3.12.0 was linear). With more than 16 cached steps, the length of the common prefix of the two paths is now computed once, and the deepest cached step that ends within it, at a token boundary of the new path, is taken. Steps that are skipped are dropped from the cache, so the search is amortized constant per token. With fewer steps, which is the usual case, they are compared as before, as that is cheaper for short paths. The operations, and which values stay valid after them, are unchanged. Measurements (Apple clang -O2, arm64), patch_inplace() of two "test" operations whose paths share n tokens and continue for n more: n 25k 50k 100k 200k before 42 ms 182 ms 1194 ms 5433 ms after 0 ms 2 ms 4 ms 8 ms 3.12.0 3 ms 5 ms 10 ms 19 ms Patches of ordinary documents (diff() then patch(), and many short operations): within 0.5 % of before in instructions retired (canada, citm_catalog, twitter, mesh, update-center, many short synthetic operations). Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
3 files changed
+495
-10
No files matched your search
Generated
+38
-5
@@ -27266,6 +27266,32 @@ class json_pointer
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
@brief the number of the deepest step of the previous path that is a prefix of the new one
|
||||
|
||||
Comparing every one of many steps with the beginning of the path would take
|
||||
quadratic time. Instead, the length of the common beginning of the two paths
|
||||
is computed once: a step whose prefix is not longer is a prefix of the new
|
||||
path, too. For few steps, which is the usual case, @ref resolve_plain
|
||||
compares them, which is cheaper.
|
||||
*/
|
||||
template<typename Steps>
|
||||
JSON_HEDLEY_NEVER_INLINE static std::size_t deepest_shared_step(const string_t& path, const std::size_t length, const string_t& previous, const Steps& steps) noexcept
|
||||
{
|
||||
const std::size_t same = common_prefix_length(path.data(), previous.data(), (std::min)(length, previous.size()));
|
||||
std::size_t depth = steps.size();
|
||||
for (; depth > 1; --depth)
|
||||
{
|
||||
// a step ends at a token boundary of the previous path
|
||||
const std::size_t prefix = steps[depth - 1].first;
|
||||
if (prefix <= same && (prefix == length || path[prefix] == '/'))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
|
||||
/*!
|
||||
@brief resolve a plain JSON pointer without building its reference tokens
|
||||
|
||||
@@ -27294,13 +27320,20 @@ class json_pointer
|
||||
if (cache.path != nullptr && !steps.empty() && steps[0].second == root)
|
||||
{
|
||||
const string_t& previous = *cache.path;
|
||||
for (depth = steps.size(); depth > 1; --depth)
|
||||
if (JSON_HEDLEY_UNLIKELY(steps.size() > 16))
|
||||
{
|
||||
// a step ends at a token boundary of the previous path
|
||||
const std::size_t prefix = steps[depth - 1].first;
|
||||
if (prefix <= length && (prefix == length || path[prefix] == '/') && std::memcmp(path.data(), previous.data(), prefix) == 0)
|
||||
depth = deepest_shared_step(path, length, previous, steps);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (depth = steps.size(); depth > 1; --depth)
|
||||
{
|
||||
break;
|
||||
// a step ends at a token boundary of the previous path
|
||||
const std::size_t prefix = steps[depth - 1].first;
|
||||
if (prefix <= length && (prefix == length || path[prefix] == '/') && std::memcmp(path.data(), previous.data(), prefix) == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
steps.resize(depth);
|
||||
|
||||
Reference in new issue
Block a user