optimization: merge SizeComputer specializations + add new ones

Endianness doesn't affect the final size, we can skip it for `SizeComputer`.
We can `if constexpr` previous calls into existing method, short-circuiting existing logic when we only need their serialized sizes.

> cmake -B build -DBUILD_BENCH=ON -DCMAKE_BUILD_TYPE=Release && cmake --build build -j$(nproc) && build/src/bench/bench_bitcoin -filter='SizeComputerBlock|SerializeBlock|DeserializeBlock' --min-time=10000

> C compiler ............................ AppleClang 16.0.0.16000026

|            ns/block |             block/s |    err% |     total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
|          977,638.68 |            1,022.87 |    0.2% |     11.00 | `DeserializeBlock`
|          196,939.81 |            5,077.69 |    0.4% |     10.94 | `SerializeBlock`
|           10,436.49 |           95,817.63 |    0.2% |     11.01 | `SizeComputerBlock`

> C++ compiler .......................... GNU 13.3.0

TODO
This commit is contained in:
Lőrinc 2025-02-14 13:54:57 +01:00
parent bca9f5e0b6
commit 8c32737387

View file

@ -48,58 +48,68 @@ static const unsigned int MAX_VECTOR_ALLOCATE = 5000000;
struct deserialize_type {}; struct deserialize_type {};
constexpr deserialize_type deserialize {}; constexpr deserialize_type deserialize {};
class SizeComputer;
//! Check if type contains a stream by seeing if it has a GetStream() method.
template<typename T>
concept ContainsStream = requires(T t) { t.GetStream(); };
template<typename T>
concept ContainsSizeComputer = ContainsStream<T> &&
std::is_same_v<std::remove_reference_t<decltype(std::declval<T>().GetStream())>, SizeComputer>;
/* /*
* Lowest-level serialization and conversion. * Lowest-level serialization and conversion.
*/ */
template<typename Stream> inline void ser_writedata8(Stream &s, uint8_t obj) template<typename Stream> void ser_writedata8(Stream &s, uint8_t obj)
{ {
s.write(std::as_bytes(std::span{&obj, 1})); s.write(std::as_bytes(std::span{&obj, 1}));
} }
template<typename Stream> inline void ser_writedata16(Stream &s, uint16_t obj) template<typename Stream> void ser_writedata16(Stream &s, uint16_t obj)
{ {
obj = htole16_internal(obj); obj = htole16_internal(obj);
s.write(std::as_bytes(std::span{&obj, 1})); s.write(std::as_bytes(std::span{&obj, 1}));
} }
template<typename Stream> inline void ser_writedata32(Stream &s, uint32_t obj) template<typename Stream> void ser_writedata32(Stream &s, uint32_t obj)
{ {
obj = htole32_internal(obj); obj = htole32_internal(obj);
s.write(std::as_bytes(std::span{&obj, 1})); s.write(std::as_bytes(std::span{&obj, 1}));
} }
template<typename Stream> inline void ser_writedata32be(Stream &s, uint32_t obj) template<typename Stream> void ser_writedata32be(Stream &s, uint32_t obj)
{ {
obj = htobe32_internal(obj); obj = htobe32_internal(obj);
s.write(std::as_bytes(std::span{&obj, 1})); s.write(std::as_bytes(std::span{&obj, 1}));
} }
template<typename Stream> inline void ser_writedata64(Stream &s, uint64_t obj) template<typename Stream> void ser_writedata64(Stream &s, uint64_t obj)
{ {
obj = htole64_internal(obj); obj = htole64_internal(obj);
s.write(std::as_bytes(std::span{&obj, 1})); s.write(std::as_bytes(std::span{&obj, 1}));
} }
template<typename Stream> inline uint8_t ser_readdata8(Stream &s) template<typename Stream> uint8_t ser_readdata8(Stream &s)
{ {
uint8_t obj; uint8_t obj;
s.read(std::as_writable_bytes(std::span{&obj, 1})); s.read(std::as_writable_bytes(std::span{&obj, 1}));
return obj; return obj;
} }
template<typename Stream> inline uint16_t ser_readdata16(Stream &s) template<typename Stream> uint16_t ser_readdata16(Stream &s)
{ {
uint16_t obj; uint16_t obj;
s.read(std::as_writable_bytes(std::span{&obj, 1})); s.read(std::as_writable_bytes(std::span{&obj, 1}));
return le16toh_internal(obj); return le16toh_internal(obj);
} }
template<typename Stream> inline uint32_t ser_readdata32(Stream &s) template<typename Stream> uint32_t ser_readdata32(Stream &s)
{ {
uint32_t obj; uint32_t obj;
s.read(std::as_writable_bytes(std::span{&obj, 1})); s.read(std::as_writable_bytes(std::span{&obj, 1}));
return le32toh_internal(obj); return le32toh_internal(obj);
} }
template<typename Stream> inline uint32_t ser_readdata32be(Stream &s) template<typename Stream> uint32_t ser_readdata32be(Stream &s)
{ {
uint32_t obj; uint32_t obj;
s.read(std::as_writable_bytes(std::span{&obj, 1})); s.read(std::as_writable_bytes(std::span{&obj, 1}));
return be32toh_internal(obj); return be32toh_internal(obj);
} }
template<typename Stream> inline uint64_t ser_readdata64(Stream &s) template<typename Stream> uint64_t ser_readdata64(Stream &s)
{ {
uint64_t obj; uint64_t obj;
s.read(std::as_writable_bytes(std::span{&obj, 1})); s.read(std::as_writable_bytes(std::span{&obj, 1}));
@ -107,8 +117,6 @@ template<typename Stream> inline uint64_t ser_readdata64(Stream &s)
} }
class SizeComputer;
/** /**
* Convert any argument to a reference to X, maintaining constness. * Convert any argument to a reference to X, maintaining constness.
* *
@ -248,7 +256,9 @@ concept ByteOrIntegral = std::is_same_v<T, std::byte> ||
template <typename Stream, CharNotInt8 V> void Serialize(Stream&, V) = delete; // char serialization forbidden. Use uint8_t or int8_t template <typename Stream, CharNotInt8 V> void Serialize(Stream&, V) = delete; // char serialization forbidden. Use uint8_t or int8_t
template <typename Stream, ByteOrIntegral T> void Serialize(Stream& s, T a) template <typename Stream, ByteOrIntegral T> void Serialize(Stream& s, T a)
{ {
if constexpr (sizeof(T) == 1) { if constexpr (ContainsSizeComputer<Stream>) {
s.GetStream().seek(sizeof(T));
} else if constexpr (sizeof(T) == 1) {
ser_writedata8(s, static_cast<uint8_t>(a)); // (u)int8_t or std::byte or bool ser_writedata8(s, static_cast<uint8_t>(a)); // (u)int8_t or std::byte or bool
} else if constexpr (sizeof(T) == 2) { } else if constexpr (sizeof(T) == 2) {
ser_writedata16(s, static_cast<uint16_t>(a)); // (u)int16_t ser_writedata16(s, static_cast<uint16_t>(a)); // (u)int16_t
@ -259,10 +269,38 @@ template <typename Stream, ByteOrIntegral T> void Serialize(Stream& s, T a)
ser_writedata64(s, static_cast<uint64_t>(a)); // (u)int64_t ser_writedata64(s, static_cast<uint64_t>(a)); // (u)int64_t
} }
} }
template <typename Stream, BasicByte B, int N> void Serialize(Stream& s, const B (&a)[N]) { s.write(MakeByteSpan(a)); } template <typename Stream, BasicByte B, int N> void Serialize(Stream& s, const B (&a)[N])
template <typename Stream, BasicByte B, std::size_t N> void Serialize(Stream& s, const std::array<B, N>& a) { s.write(MakeByteSpan(a)); } {
template <typename Stream, BasicByte B, std::size_t N> void Serialize(Stream& s, std::span<B, N> span) { s.write(std::as_bytes(span)); } if constexpr (ContainsSizeComputer<Stream>) {
template <typename Stream, BasicByte B> void Serialize(Stream& s, std::span<B> span) { s.write(std::as_bytes(span)); } s.GetStream().seek(N);
} else {
s.write(MakeByteSpan(a));
}
}
template <typename Stream, BasicByte B, std::size_t N> void Serialize(Stream& s, const std::array<B, N>& a)
{
if constexpr (ContainsSizeComputer<Stream>) {
s.GetStream().seek(N);
} else {
s.write(MakeByteSpan(a));
}
}
template <typename Stream, BasicByte B, std::size_t N> void Serialize(Stream& s, std::span<B, N> span)
{
if constexpr (ContainsSizeComputer<Stream>) {
s.GetStream().seek(N);
} else {
s.write(std::as_bytes(span));
}
}
template <typename Stream, BasicByte B> void Serialize(Stream& s, std::span<B> span)
{
if constexpr (ContainsSizeComputer<Stream>) {
s.GetStream().seek(span.size());
} else {
s.write(std::as_bytes(span));
}
}
template <typename Stream, CharNotInt8 V> void Unserialize(Stream&, V) = delete; // char serialization forbidden. Use uint8_t or int8_t template <typename Stream, CharNotInt8 V> void Unserialize(Stream&, V) = delete; // char serialization forbidden. Use uint8_t or int8_t
template <typename Stream, ByteOrIntegral T> void Unserialize(Stream& s, T& a) template <typename Stream, ByteOrIntegral T> void Unserialize(Stream& s, T& a)
@ -300,12 +338,14 @@ constexpr inline unsigned int GetSizeOfCompactSize(uint64_t nSize)
else return sizeof(unsigned char) + sizeof(uint64_t); else return sizeof(unsigned char) + sizeof(uint64_t);
} }
inline void WriteCompactSize(SizeComputer& os, uint64_t nSize);
template<typename Stream> template<typename Stream>
void WriteCompactSize(Stream& os, uint64_t nSize) void WriteCompactSize(Stream& os, uint64_t nSize)
{ {
if (nSize < 253) if constexpr (ContainsSizeComputer<Stream>)
{
os.GetStream().seek(GetSizeOfCompactSize(nSize));
}
else if (nSize < 253)
{ {
ser_writedata8(os, nSize); ser_writedata8(os, nSize);
} }
@ -412,7 +452,7 @@ struct CheckVarIntMode {
}; };
template<VarIntMode Mode, typename I> template<VarIntMode Mode, typename I>
inline unsigned int GetSizeOfVarInt(I n) constexpr unsigned int GetSizeOfVarInt(I n)
{ {
CheckVarIntMode<Mode, I>(); CheckVarIntMode<Mode, I>();
int nRet = 0; int nRet = 0;
@ -425,25 +465,26 @@ inline unsigned int GetSizeOfVarInt(I n)
return nRet; return nRet;
} }
template<typename I>
inline void WriteVarInt(SizeComputer& os, I n);
template<typename Stream, VarIntMode Mode, typename I> template<typename Stream, VarIntMode Mode, typename I>
void WriteVarInt(Stream& os, I n) void WriteVarInt(Stream& os, I n)
{ {
CheckVarIntMode<Mode, I>(); if constexpr (ContainsSizeComputer<Stream>) {
unsigned char tmp[(sizeof(n)*8+6)/7]; os.GetStream().seek(GetSizeOfVarInt<Mode, I>(n));
int len=0; } else {
while(true) { CheckVarIntMode<Mode, I>();
tmp[len] = (n & 0x7F) | (len ? 0x80 : 0x00); unsigned char tmp[(sizeof(n)*8+6)/7];
if (n <= 0x7F) int len=0;
break; while(true) {
n = (n >> 7) - 1; tmp[len] = (n & 0x7F) | (len ? 0x80 : 0x00);
len++; if (n <= 0x7F)
break;
n = (n >> 7) - 1;
len++;
}
do {
ser_writedata8(os, tmp[len]);
} while(len--);
} }
do {
ser_writedata8(os, tmp[len]);
} while(len--);
} }
template<typename Stream, VarIntMode Mode, typename I> template<typename Stream, VarIntMode Mode, typename I>
@ -532,7 +573,9 @@ struct CustomUintFormatter
template <typename Stream, typename I> void Ser(Stream& s, I v) template <typename Stream, typename I> void Ser(Stream& s, I v)
{ {
if (v < 0 || v > MAX) throw std::ios_base::failure("CustomUintFormatter value out of range"); if (v < 0 || v > MAX) throw std::ios_base::failure("CustomUintFormatter value out of range");
if (BigEndian) { if constexpr (ContainsSizeComputer<Stream>) {
s.GetStream().seek(Bytes);
} else if (BigEndian) {
uint64_t raw = htobe64_internal(v); uint64_t raw = htobe64_internal(v);
s.write(std::as_bytes(std::span{&raw, 1}).last(Bytes)); s.write(std::as_bytes(std::span{&raw, 1}).last(Bytes));
} else { } else {
@ -1063,6 +1106,9 @@ protected:
public: public:
SizeComputer() = default; SizeComputer() = default;
SizeComputer& GetStream() { return *this; }
const SizeComputer& GetStream() const { return *this; };
void write(std::span<const std::byte> src) void write(std::span<const std::byte> src)
{ {
this->nSize += src.size(); this->nSize += src.size();
@ -1086,27 +1132,12 @@ public:
} }
}; };
template<typename I>
inline void WriteVarInt(SizeComputer &s, I n)
{
s.seek(GetSizeOfVarInt<I>(n));
}
inline void WriteCompactSize(SizeComputer &s, uint64_t nSize)
{
s.seek(GetSizeOfCompactSize(nSize));
}
template <typename T> template <typename T>
size_t GetSerializeSize(const T& t) size_t GetSerializeSize(const T& t)
{ {
return (SizeComputer() << t).size(); return (SizeComputer() << t).size();
} }
//! Check if type contains a stream by seeing if has a GetStream() method.
template<typename T>
concept ContainsStream = requires(T t) { t.GetStream(); };
/** Wrapper that overrides the GetParams() function of a stream. */ /** Wrapper that overrides the GetParams() function of a stream. */
template <typename SubStream, typename Params> template <typename SubStream, typename Params>
class ParamsStream class ParamsStream