Write CBOR integers without branching on their sign or size
The sizes of a run of integers are hard to predict, and so is the type of each element in an array that mixes negative (number_integer) and non-negative (number_unsigned) values. Writing an integer branched on the type, the sign, and the 1/2/4/8-byte size class, and the mispredictions were the larger part of the time left on integer arrays. - number_integer and number_unsigned share one case of write_cbor; the major type and the argument are selected with a sign mask. - An integer's head is assembled without branches: the size class comes from a table indexed by count_leading_zeros(), and the first byte and the argument in network order are stored as one byte and one 8-byte word, of which only the head's length is kept. Heads of strings and containers keep the branching write_cbor_head(), as their lengths are mostly predictable. - Arrays are written by write_cbor_array(), kept out of line so that the registers its loop needs are not saved on every call of write_cbor. Runs of integers are encoded straight into space reserved from the sink (the new reserve()/commit() of both sinks), so the write pointer stays in a register instead of being reloaded for every value. The output is unchanged; the new test compares integer arrays at every head-size boundary, in runs shorter and longer than one reservation and broken up by other values, with an independent encoder through each sink. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
4 files changed
+499
-50
No files matched your search
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // max
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstring> // memcpy
|
||||
#include <memory> // shared_ptr, make_shared
|
||||
@@ -50,8 +51,8 @@ template<typename CharType> struct output_adapter_protocol
|
||||
template<typename CharType>
|
||||
using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;
|
||||
|
||||
/// output_vector_sink appends writes of at least this many characters with
|
||||
/// insert() instead of copying them to the space it keeps ahead
|
||||
/// the largest number of characters a writer may reserve() from a sink at once;
|
||||
/// output_vector_sink also appends writes of at least this size with insert()
|
||||
constexpr std::size_t max_reserve_size = 256;
|
||||
|
||||
/// @brief non-virtual output sink writing into a std::vector
|
||||
@@ -132,6 +133,31 @@ class output_vector_sink
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief space for writing up to @a length characters directly
|
||||
///
|
||||
/// Returns a pointer to at least @a length (at most @ref max_reserve_size)
|
||||
/// writable characters at the write position; @ref commit then keeps the
|
||||
/// first ones of them. A writer can store whole words there and keep only
|
||||
/// the bytes it needs, and keep its write pointer in a register across many
|
||||
/// values instead of updating the sink after each.
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
CharType* reserve(const std::size_t length)
|
||||
{
|
||||
JSON_ASSERT(length <= max_reserve_size);
|
||||
if (JSON_HEDLEY_UNLIKELY(v->size() - position < length))
|
||||
{
|
||||
grow(length);
|
||||
}
|
||||
return v->data() + position;
|
||||
}
|
||||
|
||||
/// keep the first @a length characters written to the space from reserve()
|
||||
JSON_HEDLEY_ALWAYS_INLINE
|
||||
void commit(const std::size_t length) noexcept
|
||||
{
|
||||
position += length;
|
||||
}
|
||||
|
||||
private:
|
||||
/// make room for at least @a length more characters after the position;
|
||||
/// kept out of line so that the write functions stay small enough for
|
||||
@@ -276,8 +302,24 @@ class output_adapter_sink
|
||||
oa->write_characters(s, length);
|
||||
}
|
||||
|
||||
/// space for writing up to @a length characters (see
|
||||
/// output_vector_sink::reserve)
|
||||
CharType* reserve(const std::size_t length) noexcept
|
||||
{
|
||||
JSON_ASSERT(length <= max_reserve_size);
|
||||
static_cast<void>(length);
|
||||
return reserved.data();
|
||||
}
|
||||
|
||||
/// write the first @a length characters written to the space from reserve()
|
||||
void commit(const std::size_t length)
|
||||
{
|
||||
oa->write_characters(reserved.data(), length);
|
||||
}
|
||||
|
||||
private:
|
||||
output_adapter_t<CharType> oa;
|
||||
std::array<CharType, max_reserve_size> reserved{};
|
||||
};
|
||||
|
||||
/// @brief whether std::basic_string<CharType> has a non-deprecated std::char_traits
|
||||
|
||||
Reference in new issue
Block a user