From 8c327373877ef4641cc2e1227bb3b8a68261cb5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Fri, 14 Feb 2025 13:54:57 +0100 Subject: [PATCH] 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 --- src/serialize.h | 135 +++++++++++++++++++++++++++++------------------- 1 file changed, 83 insertions(+), 52 deletions(-) diff --git a/src/serialize.h b/src/serialize.h index 74d549bdcac..6c296347461 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -48,58 +48,68 @@ static const unsigned int MAX_VECTOR_ALLOCATE = 5000000; struct deserialize_type {}; constexpr deserialize_type deserialize {}; +class SizeComputer; + +//! Check if type contains a stream by seeing if it has a GetStream() method. +template +concept ContainsStream = requires(T t) { t.GetStream(); }; + +template +concept ContainsSizeComputer = ContainsStream && + std::is_same_v().GetStream())>, SizeComputer>; + /* * Lowest-level serialization and conversion. */ -template inline void ser_writedata8(Stream &s, uint8_t obj) +template void ser_writedata8(Stream &s, uint8_t obj) { s.write(std::as_bytes(std::span{&obj, 1})); } -template inline void ser_writedata16(Stream &s, uint16_t obj) +template void ser_writedata16(Stream &s, uint16_t obj) { obj = htole16_internal(obj); s.write(std::as_bytes(std::span{&obj, 1})); } -template inline void ser_writedata32(Stream &s, uint32_t obj) +template void ser_writedata32(Stream &s, uint32_t obj) { obj = htole32_internal(obj); s.write(std::as_bytes(std::span{&obj, 1})); } -template inline void ser_writedata32be(Stream &s, uint32_t obj) +template void ser_writedata32be(Stream &s, uint32_t obj) { obj = htobe32_internal(obj); s.write(std::as_bytes(std::span{&obj, 1})); } -template inline void ser_writedata64(Stream &s, uint64_t obj) +template void ser_writedata64(Stream &s, uint64_t obj) { obj = htole64_internal(obj); s.write(std::as_bytes(std::span{&obj, 1})); } -template inline uint8_t ser_readdata8(Stream &s) +template uint8_t ser_readdata8(Stream &s) { uint8_t obj; s.read(std::as_writable_bytes(std::span{&obj, 1})); return obj; } -template inline uint16_t ser_readdata16(Stream &s) +template uint16_t ser_readdata16(Stream &s) { uint16_t obj; s.read(std::as_writable_bytes(std::span{&obj, 1})); return le16toh_internal(obj); } -template inline uint32_t ser_readdata32(Stream &s) +template uint32_t ser_readdata32(Stream &s) { uint32_t obj; s.read(std::as_writable_bytes(std::span{&obj, 1})); return le32toh_internal(obj); } -template inline uint32_t ser_readdata32be(Stream &s) +template uint32_t ser_readdata32be(Stream &s) { uint32_t obj; s.read(std::as_writable_bytes(std::span{&obj, 1})); return be32toh_internal(obj); } -template inline uint64_t ser_readdata64(Stream &s) +template uint64_t ser_readdata64(Stream &s) { uint64_t obj; s.read(std::as_writable_bytes(std::span{&obj, 1})); @@ -107,8 +117,6 @@ template inline uint64_t ser_readdata64(Stream &s) } -class SizeComputer; - /** * Convert any argument to a reference to X, maintaining constness. * @@ -248,7 +256,9 @@ concept ByteOrIntegral = std::is_same_v || template void Serialize(Stream&, V) = delete; // char serialization forbidden. Use uint8_t or int8_t template void Serialize(Stream& s, T a) { - if constexpr (sizeof(T) == 1) { + if constexpr (ContainsSizeComputer) { + s.GetStream().seek(sizeof(T)); + } else if constexpr (sizeof(T) == 1) { ser_writedata8(s, static_cast(a)); // (u)int8_t or std::byte or bool } else if constexpr (sizeof(T) == 2) { ser_writedata16(s, static_cast(a)); // (u)int16_t @@ -259,10 +269,38 @@ template void Serialize(Stream& s, T a) ser_writedata64(s, static_cast(a)); // (u)int64_t } } -template void Serialize(Stream& s, const B (&a)[N]) { s.write(MakeByteSpan(a)); } -template void Serialize(Stream& s, const std::array& a) { s.write(MakeByteSpan(a)); } -template void Serialize(Stream& s, std::span span) { s.write(std::as_bytes(span)); } -template void Serialize(Stream& s, std::span span) { s.write(std::as_bytes(span)); } +template void Serialize(Stream& s, const B (&a)[N]) +{ + if constexpr (ContainsSizeComputer) { + s.GetStream().seek(N); + } else { + s.write(MakeByteSpan(a)); + } +} +template void Serialize(Stream& s, const std::array& a) +{ + if constexpr (ContainsSizeComputer) { + s.GetStream().seek(N); + } else { + s.write(MakeByteSpan(a)); + } +} +template void Serialize(Stream& s, std::span span) +{ + if constexpr (ContainsSizeComputer) { + s.GetStream().seek(N); + } else { + s.write(std::as_bytes(span)); + } +} +template void Serialize(Stream& s, std::span span) +{ + if constexpr (ContainsSizeComputer) { + s.GetStream().seek(span.size()); + } else { + s.write(std::as_bytes(span)); + } +} template void Unserialize(Stream&, V) = delete; // char serialization forbidden. Use uint8_t or int8_t template 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); } -inline void WriteCompactSize(SizeComputer& os, uint64_t nSize); - template void WriteCompactSize(Stream& os, uint64_t nSize) { - if (nSize < 253) + if constexpr (ContainsSizeComputer) + { + os.GetStream().seek(GetSizeOfCompactSize(nSize)); + } + else if (nSize < 253) { ser_writedata8(os, nSize); } @@ -412,7 +452,7 @@ struct CheckVarIntMode { }; template -inline unsigned int GetSizeOfVarInt(I n) +constexpr unsigned int GetSizeOfVarInt(I n) { CheckVarIntMode(); int nRet = 0; @@ -425,25 +465,26 @@ inline unsigned int GetSizeOfVarInt(I n) return nRet; } -template -inline void WriteVarInt(SizeComputer& os, I n); - template void WriteVarInt(Stream& os, I n) { - CheckVarIntMode(); - unsigned char tmp[(sizeof(n)*8+6)/7]; - int len=0; - while(true) { - tmp[len] = (n & 0x7F) | (len ? 0x80 : 0x00); - if (n <= 0x7F) - break; - n = (n >> 7) - 1; - len++; + if constexpr (ContainsSizeComputer) { + os.GetStream().seek(GetSizeOfVarInt(n)); + } else { + CheckVarIntMode(); + unsigned char tmp[(sizeof(n)*8+6)/7]; + int len=0; + while(true) { + tmp[len] = (n & 0x7F) | (len ? 0x80 : 0x00); + 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 @@ -532,7 +573,9 @@ struct CustomUintFormatter template void Ser(Stream& s, I v) { if (v < 0 || v > MAX) throw std::ios_base::failure("CustomUintFormatter value out of range"); - if (BigEndian) { + if constexpr (ContainsSizeComputer) { + s.GetStream().seek(Bytes); + } else if (BigEndian) { uint64_t raw = htobe64_internal(v); s.write(std::as_bytes(std::span{&raw, 1}).last(Bytes)); } else { @@ -1063,6 +1106,9 @@ protected: public: SizeComputer() = default; + SizeComputer& GetStream() { return *this; } + const SizeComputer& GetStream() const { return *this; }; + void write(std::span src) { this->nSize += src.size(); @@ -1086,27 +1132,12 @@ public: } }; -template -inline void WriteVarInt(SizeComputer &s, I n) -{ - s.seek(GetSizeOfVarInt(n)); -} - -inline void WriteCompactSize(SizeComputer &s, uint64_t nSize) -{ - s.seek(GetSizeOfCompactSize(nSize)); -} - template size_t GetSerializeSize(const T& t) { return (SizeComputer() << t).size(); } -//! Check if type contains a stream by seeing if has a GetStream() method. -template -concept ContainsStream = requires(T t) { t.GetStream(); }; - /** Wrapper that overrides the GetParams() function of a stream. */ template class ParamsStream