From c02600b8e1d4f34185db53039de7a4c10d944c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Fri, 14 Feb 2025 00:53:11 +0100 Subject: [PATCH] optimization: Add single byte write Single byte writes are used very often (used for every (u)int8_t or std::byte or bool and for every VarInt's first byte which is also needed for every (pre)Vector). It makes sense to avoid the generalized serialization infrastructure that isn't needed: * AutoFile write doesn't need to allocate 4k buffer for a single byte now; * `VectorWriter` and `DataStream` avoids memcpy/insert calls. > 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 |--------------------:|--------------------:|--------:|----------:|:---------- | 934,120.45 | 1,070.53 | 0.2% | 11.01 | `DeserializeBlock` | 170,719.27 | 5,857.57 | 0.1% | 10.99 | `SerializeBlock` | 12,048.40 | 82,998.58 | 0.2% | 11.01 | `SizeComputerBlock` > C++ compiler .......................... GNU 13.3.0 | ns/block | block/s | err% | ins/block | cyc/block | IPC | bra/block | miss% | total | benchmark |--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------- | 4,433,835.04 | 225.54 | 0.0% | 53,688,481.60 | 15,918,730.23 | 3.373 | 2,409,056.47 | 0.5% | 11.01 | `DeserializeBlock` | 563,663.10 | 1,774.11 | 0.0% | 7,386,775.59 | 2,023,525.77 | 3.650 | 1,385,368.57 | 0.5% | 11.00 | `SerializeBlock` | 27,351.60 | 36,560.93 | 0.1% | 225,261.03 | 98,209.77 | 2.294 | 53,037.03 | 0.9% | 11.00 | `SizeComputerBlock` --- src/hash.h | 9 +++++++++ src/serialize.h | 7 ++++++- src/streams.cpp | 18 ++++++++++++++++++ src/streams.h | 16 ++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/hash.h b/src/hash.h index 52babf8b1d9..5e9a2bd648e 100644 --- a/src/hash.h +++ b/src/hash.h @@ -107,6 +107,10 @@ public: { ctx.Write(UCharCast(src.data()), src.size()); } + void write(std::byte src) + { + ctx.Write(UCharCast(&src), 1); + } /** Compute the double-SHA256 hash of all data written to this object. * @@ -194,6 +198,11 @@ public: m_source.write(src); HashWriter::write(src); } + void write(std::byte src) + { + m_source.write(src); + HashWriter::write(src); + } template HashedSourceWriter& operator<<(const T& obj) diff --git a/src/serialize.h b/src/serialize.h index fa85663ad31..0b75a1e3a44 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -53,7 +53,7 @@ constexpr deserialize_type deserialize {}; */ template inline void ser_writedata8(Stream &s, uint8_t obj) { - s.write(AsBytes(Span{&obj, 1})); + s.write(std::byte{obj}); } template inline void ser_writedata16(Stream &s, uint16_t obj) { @@ -1067,6 +1067,10 @@ public: { this->nSize += src.size(); } + void write(std::byte) + { + this->nSize += 1; + } /** Pretend _nSize bytes are written, without specifying them. */ void seek(size_t _nSize) @@ -1131,6 +1135,7 @@ public: template ParamsStream& operator<<(const U& obj) { ::Serialize(*this, obj); return *this; } template ParamsStream& operator>>(U&& obj) { ::Unserialize(*this, obj); return *this; } void write(Span src) { GetStream().write(src); } + void write(std::byte src) { GetStream().write(src); } void read(Span dst) { GetStream().read(dst); } void ignore(size_t num) { GetStream().ignore(num); } bool eof() const { return GetStream().eof(); } diff --git a/src/streams.cpp b/src/streams.cpp index bec505ebc3a..51b8c4aa087 100644 --- a/src/streams.cpp +++ b/src/streams.cpp @@ -111,6 +111,24 @@ void AutoFile::write_large(Span src) if (m_position) *m_position += src.size(); } +void AutoFile::write(std::byte val) +{ + if (!m_file) throw std::ios_base::failure("AutoFile::write: file handle is nullptr"); + if (!m_obfuscation) { + if (fwrite(&val, 1, 1, m_file) != 1) { + throw std::ios_base::failure("AutoFile::write: write failed"); + } + if (m_position.has_value()) *m_position += 1; + } else { + if (!m_position.has_value()) throw std::ios_base::failure("AutoFile::write: position unknown"); + auto src{Span{&val, 1}}; + m_obfuscation(src, *m_position); + if (fwrite(src.data(), 1, 1, m_file) != 1) { + throw std::ios_base::failure{"XorFile::write: failed"}; + } + *m_position += 1; + } +} bool AutoFile::Commit() { return ::FileCommit(m_file); diff --git a/src/streams.h b/src/streams.h index e1b324b52d8..b4a17702b9a 100644 --- a/src/streams.h +++ b/src/streams.h @@ -62,6 +62,16 @@ public: } nPos += src.size(); } + void write(std::byte val) + { + assert(nPos <= vchData.size()); + if (nPos < vchData.size()) { + vchData[nPos] = static_cast(val); + } else { + vchData.push_back(static_cast(val)); + } + nPos += 1; + } template VectorWriter& operator<<(const T& obj) { @@ -233,6 +243,11 @@ public: // Write to the end of the buffer vch.insert(vch.end(), src.begin(), src.end()); } + void write(value_type val) + { + // Push single value to the end of the buffer + vch.push_back(val); + } template DataStream& operator<<(const T& obj) @@ -427,6 +442,7 @@ public: void ignore(size_t nSize); void write(Span src); void write_large(Span src); // Note that src will be mutated + void write(std::byte src); template AutoFile& operator<<(const T& obj)