Reserve at least 64 bytes for the result of a binary serialization

The result of to_cbor() and its siblings reserved the lower bound of
binary_reserve_hint(), for a small object a few bytes, and the first write of
the sink replaced it by an allocation of 64 bytes, as an empty or too small
vector is grown to at least that. A small document therefore cost two
allocations and a free.

The result now reserves at least min_vector_sink_capacity (64) characters at
once. binary_reserve_hint() itself, a lower bound that is tested as such, is
unchanged.

Instructions to serialize a small object (six members, a nested array and object), Linux, GCC 13: to_cbor 1700 -> 1500, to_msgpack 1800 -> 1600, to_ubjson 1800 -> 1700, to_bson 2700 -> 2500. Documents larger than 64 bytes of hint are unchanged.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann committed 2026-10-22 09:29:11 +02:00
1 parent 7241ba94c8
commit ceb3c92709
4 files changed
+54 -18

No files matched your search

@@ -8,7 +8,7 @@
#pragma once
#include <algorithm> // min, reverse
#include <algorithm> // max, min, reverse
#include <array> // array
#include <cmath> // isnan, isinf
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
@@ -75,6 +75,21 @@ std::size_t binary_reserve_hint(const BasicJsonType& j)
return 0;
}
/*!
@brief the capacity to reserve for the result of a binary serialization into a
std::vector
The hint of @ref binary_reserve_hint, but not less than the 64 characters that
output_vector_sink grows an empty vector to on its first write anyway. A small
document then costs one allocation, instead of an allocation of a few bytes that
the first write replaces with one of 64.
*/
template<typename BasicJsonType>
std::size_t binary_initial_capacity(const BasicJsonType& j)
{
return (std::max)(binary_reserve_hint(j), min_vector_sink_capacity);
}
/*!
@brief serialization to BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values
*/
@@ -112,6 +112,9 @@ inline void copy_short(CharType* dst, const CharType* src, const std::size_t len
std::memcpy(dst, src, length * sizeof(CharType));
}
/// the number of characters an empty vector is grown to by output_vector_sink
constexpr std::size_t min_vector_sink_capacity = 64;
/// @brief non-virtual output sink writing into a std::vector
///
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
@@ -232,10 +235,10 @@ class output_vector_sink
void grow(const std::size_t length)
{
// use the capacity the vector already has, else at least double it;
// never less than 64 characters, so that a small value does not
// never less than min_vector_sink_capacity, so that a small value does not
// reallocate for every few bytes
const std::size_t needed = position + length;
v->resize((std::max)(std::size_t{64}, needed <= v->capacity()
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
? v->capacity()
: (std::max)(needed, v->capacity() * 2)));
}
+6 -6
View File
@@ -6321,7 +6321,7 @@ public:
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_reserve_hint(j));
result.reserve(detail::binary_initial_capacity(j));
vector_writer(result, error_handler).write_cbor(j);
return result;
}
@@ -6352,7 +6352,7 @@ public:
const error_handler_t error_handler = error_handler_t::keep)
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_reserve_hint(j));
result.reserve(detail::binary_initial_capacity(j));
vector_writer(result, error_handler).write_msgpack(j);
return result;
}
@@ -6385,7 +6385,7 @@ public:
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_reserve_hint(j));
result.reserve(detail::binary_initial_capacity(j));
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type);
return result;
}
@@ -6421,7 +6421,7 @@ public:
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_reserve_hint(j));
result.reserve(detail::binary_initial_capacity(j));
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
return result;
}
@@ -6456,7 +6456,7 @@ public:
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_reserve_hint(j));
result.reserve(detail::binary_initial_capacity(j));
vector_writer(result, error_handler).write_bson(j);
return result;
}
@@ -6486,7 +6486,7 @@ public:
static std::vector<std::uint8_t> to_bon8(const basic_json& j)
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_reserve_hint(j));
result.reserve(detail::binary_initial_capacity(j));
vector_writer(result).write_bon8(j);
return result;
}
+27 -9
View File
@@ -24241,7 +24241,7 @@ NLOHMANN_JSON_NAMESPACE_END
#include <algorithm> // min, reverse
#include <algorithm> // max, min, reverse
#include <array> // array
#include <cmath> // isnan, isinf
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
@@ -24378,6 +24378,9 @@ inline void copy_short(CharType* dst, const CharType* src, const std::size_t len
std::memcpy(dst, src, length * sizeof(CharType));
}
/// the number of characters an empty vector is grown to by output_vector_sink
constexpr std::size_t min_vector_sink_capacity = 64;
/// @brief non-virtual output sink writing into a std::vector
///
/// This sink is not part of the virtual output_adapter_protocol hierarchy: it is
@@ -24498,10 +24501,10 @@ class output_vector_sink
void grow(const std::size_t length)
{
// use the capacity the vector already has, else at least double it;
// never less than 64 characters, so that a small value does not
// never less than min_vector_sink_capacity, so that a small value does not
// reallocate for every few bytes
const std::size_t needed = position + length;
v->resize((std::max)(std::size_t{64}, needed <= v->capacity()
v->resize((std::max)(min_vector_sink_capacity, needed <= v->capacity()
? v->capacity()
: (std::max)(needed, v->capacity() * 2)));
}
@@ -24903,6 +24906,21 @@ std::size_t binary_reserve_hint(const BasicJsonType& j)
return 0;
}
/*!
@brief the capacity to reserve for the result of a binary serialization into a
std::vector
The hint of @ref binary_reserve_hint, but not less than the 64 characters that
output_vector_sink grows an empty vector to on its first write anyway. A small
document then costs one allocation, instead of an allocation of a few bytes that
the first write replaces with one of 64.
*/
template<typename BasicJsonType>
std::size_t binary_initial_capacity(const BasicJsonType& j)
{
return (std::max)(binary_reserve_hint(j), min_vector_sink_capacity);
}
/*!
@brief serialization to BJData, BON8, BSON, CBOR, MessagePack, and UBJSON values
*/
@@ -40035,7 +40053,7 @@ public:
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_reserve_hint(j));
result.reserve(detail::binary_initial_capacity(j));
vector_writer(result, error_handler).write_cbor(j);
return result;
}
@@ -40066,7 +40084,7 @@ public:
const error_handler_t error_handler = error_handler_t::keep)
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_reserve_hint(j));
result.reserve(detail::binary_initial_capacity(j));
vector_writer(result, error_handler).write_msgpack(j);
return result;
}
@@ -40099,7 +40117,7 @@ public:
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_reserve_hint(j));
result.reserve(detail::binary_initial_capacity(j));
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type);
return result;
}
@@ -40135,7 +40153,7 @@ public:
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_reserve_hint(j));
result.reserve(detail::binary_initial_capacity(j));
vector_writer(result, error_handler).write_ubjson(j, use_size, use_type, true, true, version);
return result;
}
@@ -40170,7 +40188,7 @@ public:
const error_handler_t error_handler = detail::binary_writer_default_error_handler())
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_reserve_hint(j));
result.reserve(detail::binary_initial_capacity(j));
vector_writer(result, error_handler).write_bson(j);
return result;
}
@@ -40200,7 +40218,7 @@ public:
static std::vector<std::uint8_t> to_bon8(const basic_json& j)
{
std::vector<std::uint8_t> result;
result.reserve(detail::binary_reserve_hint(j));
result.reserve(detail::binary_initial_capacity(j));
vector_writer(result).write_bon8(j);
return result;
}