Grow the decode arena through a function and store its position back on every return

slow_string() kept its write position in locals and grew the arena through
the macro NLOHMANN_VIEW_ENSURE. The macro is now the always-inline member
arena_ensure() (a lambda is not inlined at every call site by clang, which
then keeps the position in memory: +11 % instructions on escape-heavy
strings), and a guard stores the position back into m_arena_write on every
return instead of by hand on the successful one. Instruction counts are
unchanged (clang) or 1 % lower (GCC).

Also word the complexity of back() as that of at().

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-23 07:59:31 +02:00
1 parent 0ad42e42c0
commit d96697c285
3 files changed
+109 -35

No files matched your search

+1 -1
View File
@@ -28,7 +28,7 @@ This exception does not carry a [`JSON_DIAGNOSTICS`](../macros/json_diagnostics.
Linear in the size of the array or object: unlike [`front()`](front.md), which only ever looks at the first Linear in the size of the array or object: unlike [`front()`](front.md), which only ever looks at the first
element, `back()` has to walk every element to find where they end, since elements are not a fixed size in the element, `back()` has to walk every element to find where they end, since elements are not a fixed size in the
index. Constant for an array none of whose elements is an array or object, since the elements are then adjacent index. Constant if no element of the array is an array or object, since the elements are then adjacent
in the index. in the index.
## Notes ## Notes
+54 -17
View File
@@ -277,26 +277,25 @@ class builder
}; };
// The write position is kept in locals: the stores of characters into // The write position is kept in locals: the stores of characters into
// the arena may alias the members of the builder, which would then be // the arena may alias the members of the builder, which would then be
// reloaded after every one. It is stored back where the arena grows // reloaded after every one. It is stored back where the arena grows,
// and at the end. // and by `write_back` on every return.
const unsigned char* const end = m_end; const unsigned char* const end = m_end;
const std::size_t start = arena_used(); const std::size_t start = arena_used();
char* w = m_arena_write; char* w = m_arena_write;
const char* w_end = m_arena_end; const char* w_end = m_arena_end;
#define NLOHMANN_VIEW_ENSURE(n) \ const arena_write_back write_back(*this, w);
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(w_end - w) < (n))) { m_arena_write = w; arena_grow(n); w = m_arena_write; w_end = m_arena_end; }
{ {
const auto n = static_cast<std::size_t>(p - s); const auto n = static_cast<std::size_t>(p - s);
if (n <= 16 && end - s >= 16) if (n <= 16 && end - s >= 16)
{ {
NLOHMANN_VIEW_ENSURE(16) arena_ensure(w, w_end, 16);
std::memcpy(w, s, 16); std::memcpy(w, s, 16);
} }
else if (n != 0) else if (n != 0)
{ {
// (an empty run leaves an empty arena unallocated, and memcpy() // (an empty run leaves an empty arena unallocated, and memcpy()
// must not be passed its null pointer even for zero bytes) // must not be passed its null pointer even for zero bytes)
NLOHMANN_VIEW_ENSURE(n) arena_ensure(w, w_end, n);
std::memcpy(w, s, n); std::memcpy(w, s, n);
} }
w += n; w += n;
@@ -311,8 +310,9 @@ class builder
if (c == '"') if (c == '"')
{ {
++p; ++p;
m_arena_write = w; // (from w: `write_back` stores it only after the return value
return decoded{p, start, arena_used() - start}; // is made; the arena is allocated, since an escape was written)
return decoded{p, start, static_cast<std::size_t>(w - m_doc.arena.data()) - start};
} }
if (c != '\\') if (c != '\\')
{ {
@@ -356,12 +356,12 @@ class builder
} }
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00); cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
} }
NLOHMANN_VIEW_ENSURE(4) arena_ensure(w, w_end, 4);
w = put_utf8(w, cp); w = put_utf8(w, cp);
} }
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0)) else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
{ {
NLOHMANN_VIEW_ENSURE(1) arena_ensure(w, w_end, 1);
*w++ = simple_escape[d]; *w++ = simple_escape[d];
} }
else else
@@ -387,7 +387,7 @@ class builder
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP) NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
#undef NLOHMANN_VIEW_STEP #undef NLOHMANN_VIEW_STEP
run_found: run_found:
NLOHMANN_VIEW_ENSURE(16) arena_ensure(w, w_end, 16);
std::memcpy(w, p, 16); // (only the first `steps` bytes are kept) std::memcpy(w, p, 16); // (only the first `steps` bytes are kept)
w += steps; w += steps;
p += steps; p += steps;
@@ -399,7 +399,7 @@ run_found:
const unsigned char* const r = p; const unsigned char* const r = p;
p = scan_string_run(p, end); p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r); const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n) arena_ensure(w, w_end, n);
std::memcpy(w, r, n); std::memcpy(w, r, n);
w += n; w += n;
} }
@@ -423,12 +423,12 @@ run_found:
const unsigned char* const r = p; const unsigned char* const r = p;
p = scan_string_run(p, end); p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r); const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n) arena_ensure(w, w_end, n);
std::memcpy(w, r, n); std::memcpy(w, r, n);
w += n; w += n;
break; break;
} }
NLOHMANN_VIEW_ENSURE(16) arena_ensure(w, w_end, 16);
const unsigned k = copy_plain_run16(p, w); const unsigned k = copy_plain_run16(p, w);
w += k; w += k;
p += k; p += k;
@@ -442,7 +442,7 @@ run_found:
const unsigned char* const r = p; const unsigned char* const r = p;
p = scan_string_run(p, end); p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r); const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n) arena_ensure(w, w_end, n);
std::memcpy(w, r, n); std::memcpy(w, r, n);
w += n; w += n;
} }
@@ -452,12 +452,11 @@ run_found:
const unsigned char* const r = p; const unsigned char* const r = p;
p = scan_string_run(p, end); p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r); const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n) arena_ensure(w, w_end, n);
std::memcpy(w, r, n); std::memcpy(w, r, n);
w += n; w += n;
#endif #endif
} }
#undef NLOHMANN_VIEW_ENSURE
} }
/// does the float token [s, p) overflow FloatType? (parse() rejects it) /// does the float token [s, p) overflow FloatType? (parse() rejects it)
@@ -498,6 +497,44 @@ run_found:
char* m_arena_write = nullptr; char* m_arena_write = nullptr;
char* m_arena_end = nullptr; char* m_arena_end = nullptr;
/// room for n more bytes at w, the write position kept in a local, with
/// w_end the end of the arena. (A lambda instead is not always inlined,
/// and then w would be kept in memory, as m_arena_write is.)
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(char*& w, const char*& w_end, std::size_t n)
{
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(w_end - w) < n))
{
m_arena_write = w;
arena_grow(n);
w = m_arena_write;
w_end = m_arena_end;
}
}
/// stores a write position kept in a local back into m_arena_write when
/// the function that keeps it returns, on every path
class arena_write_back
{
public:
arena_write_back(builder& b, char*& w) noexcept
: m_builder(b)
, m_w(w)
{}
~arena_write_back()
{
m_builder.m_arena_write = m_w;
}
arena_write_back(const arena_write_back&) = delete;
arena_write_back& operator=(const arena_write_back&) = delete;
arena_write_back(arena_write_back&&) = delete;
arena_write_back& operator=(arena_write_back&&) = delete;
private:
builder& m_builder;
char*& m_w;
};
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t arena_used() const noexcept NLOHMANN_VIEW_ALWAYS_INLINE std::size_t arena_used() const noexcept
{ {
return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0; return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0;
+54 -17
View File
@@ -1468,26 +1468,25 @@ class builder
}; };
// The write position is kept in locals: the stores of characters into // The write position is kept in locals: the stores of characters into
// the arena may alias the members of the builder, which would then be // the arena may alias the members of the builder, which would then be
// reloaded after every one. It is stored back where the arena grows // reloaded after every one. It is stored back where the arena grows,
// and at the end. // and by `write_back` on every return.
const unsigned char* const end = m_end; const unsigned char* const end = m_end;
const std::size_t start = arena_used(); const std::size_t start = arena_used();
char* w = m_arena_write; char* w = m_arena_write;
const char* w_end = m_arena_end; const char* w_end = m_arena_end;
#define NLOHMANN_VIEW_ENSURE(n) \ const arena_write_back write_back(*this, w);
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(w_end - w) < (n))) { m_arena_write = w; arena_grow(n); w = m_arena_write; w_end = m_arena_end; }
{ {
const auto n = static_cast<std::size_t>(p - s); const auto n = static_cast<std::size_t>(p - s);
if (n <= 16 && end - s >= 16) if (n <= 16 && end - s >= 16)
{ {
NLOHMANN_VIEW_ENSURE(16) arena_ensure(w, w_end, 16);
std::memcpy(w, s, 16); std::memcpy(w, s, 16);
} }
else if (n != 0) else if (n != 0)
{ {
// (an empty run leaves an empty arena unallocated, and memcpy() // (an empty run leaves an empty arena unallocated, and memcpy()
// must not be passed its null pointer even for zero bytes) // must not be passed its null pointer even for zero bytes)
NLOHMANN_VIEW_ENSURE(n) arena_ensure(w, w_end, n);
std::memcpy(w, s, n); std::memcpy(w, s, n);
} }
w += n; w += n;
@@ -1502,8 +1501,9 @@ class builder
if (c == '"') if (c == '"')
{ {
++p; ++p;
m_arena_write = w; // (from w: `write_back` stores it only after the return value
return decoded{p, start, arena_used() - start}; // is made; the arena is allocated, since an escape was written)
return decoded{p, start, static_cast<std::size_t>(w - m_doc.arena.data()) - start};
} }
if (c != '\\') if (c != '\\')
{ {
@@ -1547,12 +1547,12 @@ class builder
} }
cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00); cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00);
} }
NLOHMANN_VIEW_ENSURE(4) arena_ensure(w, w_end, 4);
w = put_utf8(w, cp); w = put_utf8(w, cp);
} }
else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0)) else if (NLOHMANN_VIEW_LIKELY(d < 128 && simple_escape[d] != 0))
{ {
NLOHMANN_VIEW_ENSURE(1) arena_ensure(w, w_end, 1);
*w++ = simple_escape[d]; *w++ = simple_escape[d];
} }
else else
@@ -1578,7 +1578,7 @@ class builder
NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP) NLOHMANN_VIEW_REPEAT16(NLOHMANN_VIEW_STEP)
#undef NLOHMANN_VIEW_STEP #undef NLOHMANN_VIEW_STEP
run_found: run_found:
NLOHMANN_VIEW_ENSURE(16) arena_ensure(w, w_end, 16);
std::memcpy(w, p, 16); // (only the first `steps` bytes are kept) std::memcpy(w, p, 16); // (only the first `steps` bytes are kept)
w += steps; w += steps;
p += steps; p += steps;
@@ -1590,7 +1590,7 @@ run_found:
const unsigned char* const r = p; const unsigned char* const r = p;
p = scan_string_run(p, end); p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r); const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n) arena_ensure(w, w_end, n);
std::memcpy(w, r, n); std::memcpy(w, r, n);
w += n; w += n;
} }
@@ -1614,12 +1614,12 @@ run_found:
const unsigned char* const r = p; const unsigned char* const r = p;
p = scan_string_run(p, end); p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r); const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n) arena_ensure(w, w_end, n);
std::memcpy(w, r, n); std::memcpy(w, r, n);
w += n; w += n;
break; break;
} }
NLOHMANN_VIEW_ENSURE(16) arena_ensure(w, w_end, 16);
const unsigned k = copy_plain_run16(p, w); const unsigned k = copy_plain_run16(p, w);
w += k; w += k;
p += k; p += k;
@@ -1633,7 +1633,7 @@ run_found:
const unsigned char* const r = p; const unsigned char* const r = p;
p = scan_string_run(p, end); p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r); const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n) arena_ensure(w, w_end, n);
std::memcpy(w, r, n); std::memcpy(w, r, n);
w += n; w += n;
} }
@@ -1643,12 +1643,11 @@ run_found:
const unsigned char* const r = p; const unsigned char* const r = p;
p = scan_string_run(p, end); p = scan_string_run(p, end);
const auto n = static_cast<std::size_t>(p - r); const auto n = static_cast<std::size_t>(p - r);
NLOHMANN_VIEW_ENSURE(n) arena_ensure(w, w_end, n);
std::memcpy(w, r, n); std::memcpy(w, r, n);
w += n; w += n;
#endif #endif
} }
#undef NLOHMANN_VIEW_ENSURE
} }
/// does the float token [s, p) overflow FloatType? (parse() rejects it) /// does the float token [s, p) overflow FloatType? (parse() rejects it)
@@ -1689,6 +1688,44 @@ run_found:
char* m_arena_write = nullptr; char* m_arena_write = nullptr;
char* m_arena_end = nullptr; char* m_arena_end = nullptr;
/// room for n more bytes at w, the write position kept in a local, with
/// w_end the end of the arena. (A lambda instead is not always inlined,
/// and then w would be kept in memory, as m_arena_write is.)
NLOHMANN_VIEW_ALWAYS_INLINE void arena_ensure(char*& w, const char*& w_end, std::size_t n)
{
if (NLOHMANN_VIEW_UNLIKELY(static_cast<std::size_t>(w_end - w) < n))
{
m_arena_write = w;
arena_grow(n);
w = m_arena_write;
w_end = m_arena_end;
}
}
/// stores a write position kept in a local back into m_arena_write when
/// the function that keeps it returns, on every path
class arena_write_back
{
public:
arena_write_back(builder& b, char*& w) noexcept
: m_builder(b)
, m_w(w)
{}
~arena_write_back()
{
m_builder.m_arena_write = m_w;
}
arena_write_back(const arena_write_back&) = delete;
arena_write_back& operator=(const arena_write_back&) = delete;
arena_write_back(arena_write_back&&) = delete;
arena_write_back& operator=(arena_write_back&&) = delete;
private:
builder& m_builder;
char*& m_w;
};
NLOHMANN_VIEW_ALWAYS_INLINE std::size_t arena_used() const noexcept NLOHMANN_VIEW_ALWAYS_INLINE std::size_t arena_used() const noexcept
{ {
return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0; return m_arena_write != nullptr ? static_cast<std::size_t>(m_arena_write - m_doc.arena.data()) : 0;