Collect the writes of the adapter sink and pass them on in blocks

to_cbor(j, o) and its siblings with a std::string, a std::vector or a
std::ostream go through output_adapter_sink, where every write is a virtual
call, and for a stream a call with a sentry on top. That costs more than
encoding the few bytes of most values, which is why those overloads were 1.8
to 3 times slower than the vector-returning ones.

The sink now keeps a 1 KiB buffer and passes it on when it is full, when a
write is larger than the space left (a payload of a KiB or more goes
directly), or when binary_writer::flush() is called at the end of the to_*
function. Everything reaches the adapter in the same order. The destructor
flushes what is left, so a value that throws (invalid UTF-8 with the strict
handler) leaves the same partial output as before; only flush() can report an
error of the adapter, as a destructor must not throw.

Instructions per call (Linux, GCC 13, thousands), to_*(j, std::string&): twitter cbor 4952 -> 3356, twitter ubjson 6762 -> 4647, twitter bson 5518 -> 4704, citm_catalog cbor 10404 -> 7090; to_*(j, std::ostream&): twitter cbor 9178 -> 3844, twitter ubjson 11974 -> 5129, citm_catalog cbor 18374 -> 7554.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-22 09:28:46 +02:00
1 parent ef2fee20d8
commit e82ee7375a
4 files changed
+308 -50

No files matched your search

@@ -123,9 +123,22 @@ class binary_writer
template < typename SinkType = OutputSinkType,
typename std::enable_if < std::is_constructible<SinkType, output_adapter_t<CharType>>::value, int >::type = 0 >
explicit binary_writer(output_adapter_t<CharType> adapter, const error_handler_t error_handler_ = binary_writer_default_error_handler())
: oa(SinkType(std::move(adapter))), error_handler(error_handler_)
: oa(std::move(adapter)), error_handler(error_handler_)
{}
/*!
@brief pass on everything written so far to the output adapter
A writer over an output_adapter_sink buffers what it writes; this has to
be called after the last write, or the buffered end is only passed on, and
an error of the adapter lost, when the writer is destroyed. A writer over
an output_vector_sink writes straight into the vector.
*/
void flush()
{
oa.flush();
}
/*!
@param[in] j JSON value to serialize
@throw type_error.316 if a string value or an object key is not valid