From a9d311c54e6b4dd39d2ae4b8fe31b9261f7e1fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Sun, 9 Mar 2025 21:33:36 +0100 Subject: [PATCH] optimization: Add single byte writes 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 |--------------------:|--------------------:|--------:|----------:|:---------- | 847,934.56 | 1,179.34 | 0.2% | 10.82 | `DeserializeBlock` | 178,475.58 | 5,603.01 | 0.3% | 11.03 | `SerializeBlock` | 10,234.19 | 97,711.73 | 0.1% | 11.02 | `SizeComputerBlock` > C++ compiler .......................... GNU 13.3.0 TODO --- src/bench/checkblock.cpp | 4 ++-- src/bench/rpc_blockchain.cpp | 2 +- src/crypto/sha256.cpp | 15 +++++++++++++++ src/crypto/sha256.h | 1 + src/hash.h | 24 +++++++++++++++++++++++- src/serialize.h | 35 ++++++++++++++++++++--------------- src/streams.cpp | 25 +++++++++++++++++++++++++ src/streams.h | 17 +++++++++++++++++ src/test/crypto_tests.cpp | 2 +- src/test/fuzz/autofile.cpp | 4 ++-- 10 files changed, 107 insertions(+), 22 deletions(-) diff --git a/src/bench/checkblock.cpp b/src/bench/checkblock.cpp index ba26457b31a..2d9eac12339 100644 --- a/src/bench/checkblock.cpp +++ b/src/bench/checkblock.cpp @@ -52,7 +52,7 @@ static void DeserializeBlock(benchmark::Bench& bench) { DataStream stream(benchmark::data::block413567); std::byte a{0}; - stream.write({&a, 1}); // Prevent compaction + stream.write(std::span{&a, 1}); // Prevent compaction bench.unit("block").run([&] { CBlock block; @@ -66,7 +66,7 @@ static void DeserializeAndCheckBlock(benchmark::Bench& bench) { DataStream stream(benchmark::data::block413567); std::byte a{0}; - stream.write({&a, 1}); // Prevent compaction + stream.write(std::span{&a, 1}); // Prevent compaction ArgsManager bench_args; const auto chainParams = CreateChainParams(bench_args, ChainType::MAIN); diff --git a/src/bench/rpc_blockchain.cpp b/src/bench/rpc_blockchain.cpp index df951a14e49..a5d28b4a60b 100644 --- a/src/bench/rpc_blockchain.cpp +++ b/src/bench/rpc_blockchain.cpp @@ -33,7 +33,7 @@ struct TestBlockAndIndex { { DataStream stream{benchmark::data::block413567}; std::byte a{0}; - stream.write({&a, 1}); // Prevent compaction + stream.write(std::span{&a, 1}); // Prevent compaction stream >> TX_WITH_WITNESS(block); diff --git a/src/crypto/sha256.cpp b/src/crypto/sha256.cpp index 09c5d3123e8..6cf77849aac 100644 --- a/src/crypto/sha256.cpp +++ b/src/crypto/sha256.cpp @@ -723,6 +723,21 @@ CSHA256& CSHA256::Write(const unsigned char* data, size_t len) } return *this; } +CSHA256& CSHA256::Write(unsigned char data) +{ + size_t bufsize = bytes % 64; + + // Add the single byte to the buffer + buf[bufsize] = data; + bytes += 1; + + if (bufsize == 63) { + // Process the buffer if full + Transform(s, buf, 1); + } + + return *this; +} void CSHA256::Finalize(unsigned char hash[OUTPUT_SIZE]) { diff --git a/src/crypto/sha256.h b/src/crypto/sha256.h index b1348631d32..16aae54aa23 100644 --- a/src/crypto/sha256.h +++ b/src/crypto/sha256.h @@ -22,6 +22,7 @@ public: CSHA256(); CSHA256& Write(const unsigned char* data, size_t len); + CSHA256& Write(unsigned char data); void Finalize(unsigned char hash[OUTPUT_SIZE]); CSHA256& Reset(); }; diff --git a/src/hash.h b/src/hash.h index 34486af64a1..da3b1ab1145 100644 --- a/src/hash.h +++ b/src/hash.h @@ -38,6 +38,10 @@ public: sha.Write(input.data(), input.size()); return *this; } + CHash256& Write(std::span input) { + sha.Write(input[0]); + return *this; + } CHash256& Reset() { sha.Reset(); @@ -63,6 +67,10 @@ public: sha.Write(input.data(), input.size()); return *this; } + CHash160& Write(std::span input) { + sha.Write(input[0]); + return *this; + } CHash160& Reset() { sha.Reset(); @@ -107,6 +115,10 @@ public: { ctx.Write(UCharCast(src.data()), src.size()); } + void write(std::span src) + { + ctx.Write(*UCharCast(&src[0])); + } /** Compute the double-SHA256 hash of all data written to this object. * @@ -160,13 +172,18 @@ public: m_source.read(dst); this->write(dst); } + void read(std::span dst) + { + m_source.read(dst); + this->write(std::span{dst}); + } void ignore(size_t num_bytes) { std::byte data[1024]; while (num_bytes > 0) { size_t now = std::min(num_bytes, 1024); - read({data, now}); + read(std::span{data, now}); num_bytes -= now; } } @@ -194,6 +211,11 @@ public: m_source.write(src); HashWriter::write(src); } + void write(std::span 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 6c296347461..8fe8a577129 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -63,56 +63,56 @@ concept ContainsSizeComputer = ContainsStream && */ template 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 void ser_writedata16(Stream &s, uint16_t obj) { obj = htole16_internal(obj); - s.write(std::as_bytes(std::span{&obj, 1})); + s.write(std::as_bytes(std::span{&obj, 1})); } template void ser_writedata32(Stream &s, uint32_t obj) { obj = htole32_internal(obj); - s.write(std::as_bytes(std::span{&obj, 1})); + s.write(std::as_bytes(std::span{&obj, 1})); } template void ser_writedata32be(Stream &s, uint32_t obj) { obj = htobe32_internal(obj); - s.write(std::as_bytes(std::span{&obj, 1})); + s.write(std::as_bytes(std::span{&obj, 1})); } template void ser_writedata64(Stream &s, uint64_t obj) { obj = htole64_internal(obj); - s.write(std::as_bytes(std::span{&obj, 1})); + s.write(std::as_bytes(std::span{&obj, 1})); } template uint8_t ser_readdata8(Stream &s) { 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; } template uint16_t ser_readdata16(Stream &s) { 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); } template uint32_t ser_readdata32(Stream &s) { 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); } template uint32_t ser_readdata32be(Stream &s) { 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); } template uint64_t ser_readdata64(Stream &s) { uint64_t obj; - s.read(std::as_writable_bytes(std::span{&obj, 1})); + s.read(std::as_writable_bytes(std::span{&obj, 1})); return le64toh_internal(obj); } @@ -319,7 +319,6 @@ template void Unserialize(Stream& s, T& a) template void Unserialize(Stream& s, B (&a)[N]) { s.read(MakeWritableByteSpan(a)); } template void Unserialize(Stream& s, std::array& a) { s.read(MakeWritableByteSpan(a)); } template void Unserialize(Stream& s, std::span span) { s.read(std::as_writable_bytes(span)); } -template void Unserialize(Stream& s, std::span span) { s.read(std::as_writable_bytes(span)); } // clang-format on @@ -577,10 +576,10 @@ struct CustomUintFormatter s.GetStream().seek(Bytes); } else if (BigEndian) { 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}).template last()); } else { uint64_t raw = htole64_internal(v); - s.write(std::as_bytes(std::span{&raw, 1}).first(Bytes)); + s.write(std::as_bytes(std::span{&raw, 1}).template first()); } } @@ -590,10 +589,10 @@ struct CustomUintFormatter static_assert(std::numeric_limits::max() >= MAX && std::numeric_limits::min() <= 0, "Assigned type too small"); uint64_t raw = 0; if (BigEndian) { - s.read(std::as_writable_bytes(std::span{&raw, 1}).last(Bytes)); + s.read(std::as_writable_bytes(std::span{&raw, 1}).last()); v = static_cast(be64toh_internal(raw)); } else { - s.read(std::as_writable_bytes(std::span{&raw, 1}).first(Bytes)); + s.read(std::as_writable_bytes(std::span{&raw, 1}).first()); v = static_cast(le64toh_internal(raw)); } } @@ -1113,6 +1112,10 @@ public: { this->nSize += src.size(); } + void write(std::span) + { + this->nSize += 1; + } /** Pretend _nSize bytes are written, without specifying them. */ void seek(size_t _nSize) @@ -1162,7 +1165,9 @@ public: template ParamsStream& operator<<(const U& obj) { ::Serialize(*this, obj); return *this; } template ParamsStream& operator>>(U&& obj) { ::Unserialize(*this, obj); return *this; } void write(std::span src) { GetStream().write(src); } + void write(std::span src) { GetStream().write(src); } void read(std::span dst) { GetStream().read(dst); } + void read(std::span dst) { GetStream().read(dst); } void ignore(size_t num) { GetStream().ignore(num); } bool eof() const { return GetStream().eof(); } size_t size() const { return GetStream().size(); } diff --git a/src/streams.cpp b/src/streams.cpp index d82824ee583..33501c77862 100644 --- a/src/streams.cpp +++ b/src/streams.cpp @@ -63,6 +63,12 @@ void AutoFile::read(std::span dst) throw std::ios_base::failure(feof() ? "AutoFile::read: end of file" : "AutoFile::read: fread failed"); } } +void AutoFile::read(std::span dst) +{ + if (detail_fread(dst) != 1) { + throw std::ios_base::failure(feof() ? "AutoFile::read: end of file" : "AutoFile::read: fread failed"); + } +} void AutoFile::ignore(size_t nSize) { @@ -101,6 +107,25 @@ void AutoFile::write(std::span src) } } } +void AutoFile::write(std::span src) +{ + if (!m_file) throw std::ios_base::failure("AutoFile::write: file handle is nullptr"); + if (m_xor.empty()) { + if (std::fwrite(src.data(), 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"); + std::byte temp_byte = src[0]; + std::span val(&temp_byte, 1); + util::Xor(val, m_xor, *m_position); + if (fwrite(val.data(), 1, 1, m_file) != 1) { + throw std::ios_base::failure{"XorFile::write: failed"}; + } + *m_position += 1; + } +} bool AutoFile::Commit() { diff --git a/src/streams.h b/src/streams.h index 883976ee7a0..74415bb1af9 100644 --- a/src/streams.h +++ b/src/streams.h @@ -83,6 +83,17 @@ public: } nPos += src.size(); } + void write(std::span src) + { + assert(nPos <= vchData.size()); + const auto byte{*UCharCast(&src[0])}; + if (nPos < vchData.size()) { + vchData[nPos] = byte; + } else { + vchData.push_back(byte); + } + nPos += 1; + } template VectorWriter& operator<<(const T& obj) { @@ -254,6 +265,10 @@ public: // Write to the end of the buffer vch.insert(vch.end(), src.begin(), src.end()); } + void write(std::span src) + { + vch.push_back(src[0]); + } template DataStream& operator<<(const T& obj) @@ -450,8 +465,10 @@ public: // Stream subset // void read(std::span dst); + void read(std::span dst); void ignore(size_t nSize); void write(std::span src); + void write(std::span src); template AutoFile& operator<<(const T& obj) diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 5588d4cdbc6..0aab9ef0e77 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -1079,7 +1079,7 @@ BOOST_AUTO_TEST_CASE(sha256d64) in[j] = m_rng.randbits(8); } for (int j = 0; j < i; ++j) { - CHash256().Write({in + 64 * j, 64}).Finalize({out1 + 32 * j, 32}); + CHash256().Write(std::span{in + 64 * j, 64}).Finalize({out1 + 32 * j, 32}); } SHA256D64(out2, in, i); BOOST_CHECK(memcmp(out1, out2, 32 * i) == 0); diff --git a/src/test/fuzz/autofile.cpp b/src/test/fuzz/autofile.cpp index 81761c7bf96..f89b617479d 100644 --- a/src/test/fuzz/autofile.cpp +++ b/src/test/fuzz/autofile.cpp @@ -29,14 +29,14 @@ FUZZ_TARGET(autofile) [&] { std::array arr{}; try { - auto_file.read({arr.data(), fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)}); + auto_file.read(std::span{arr.data(), fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)}); } catch (const std::ios_base::failure&) { } }, [&] { const std::array arr{}; try { - auto_file.write({arr.data(), fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)}); + auto_file.write(std::span{arr.data(), fuzzed_data_provider.ConsumeIntegralInRange(0, 4096)}); } catch (const std::ios_base::failure&) { } },