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
This commit is contained in:
Lőrinc 2025-03-09 21:33:36 +01:00
parent 8c32737387
commit a9d311c54e
10 changed files with 107 additions and 22 deletions

View file

@ -52,7 +52,7 @@ static void DeserializeBlock(benchmark::Bench& bench)
{ {
DataStream stream(benchmark::data::block413567); DataStream stream(benchmark::data::block413567);
std::byte a{0}; std::byte a{0};
stream.write({&a, 1}); // Prevent compaction stream.write(std::span{&a, 1}); // Prevent compaction
bench.unit("block").run([&] { bench.unit("block").run([&] {
CBlock block; CBlock block;
@ -66,7 +66,7 @@ static void DeserializeAndCheckBlock(benchmark::Bench& bench)
{ {
DataStream stream(benchmark::data::block413567); DataStream stream(benchmark::data::block413567);
std::byte a{0}; std::byte a{0};
stream.write({&a, 1}); // Prevent compaction stream.write(std::span{&a, 1}); // Prevent compaction
ArgsManager bench_args; ArgsManager bench_args;
const auto chainParams = CreateChainParams(bench_args, ChainType::MAIN); const auto chainParams = CreateChainParams(bench_args, ChainType::MAIN);

View file

@ -33,7 +33,7 @@ struct TestBlockAndIndex {
{ {
DataStream stream{benchmark::data::block413567}; DataStream stream{benchmark::data::block413567};
std::byte a{0}; std::byte a{0};
stream.write({&a, 1}); // Prevent compaction stream.write(std::span{&a, 1}); // Prevent compaction
stream >> TX_WITH_WITNESS(block); stream >> TX_WITH_WITNESS(block);

View file

@ -723,6 +723,21 @@ CSHA256& CSHA256::Write(const unsigned char* data, size_t len)
} }
return *this; 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]) void CSHA256::Finalize(unsigned char hash[OUTPUT_SIZE])
{ {

View file

@ -22,6 +22,7 @@ public:
CSHA256(); CSHA256();
CSHA256& Write(const unsigned char* data, size_t len); CSHA256& Write(const unsigned char* data, size_t len);
CSHA256& Write(unsigned char data);
void Finalize(unsigned char hash[OUTPUT_SIZE]); void Finalize(unsigned char hash[OUTPUT_SIZE]);
CSHA256& Reset(); CSHA256& Reset();
}; };

View file

@ -38,6 +38,10 @@ public:
sha.Write(input.data(), input.size()); sha.Write(input.data(), input.size());
return *this; return *this;
} }
CHash256& Write(std::span<const unsigned char, 1> input) {
sha.Write(input[0]);
return *this;
}
CHash256& Reset() { CHash256& Reset() {
sha.Reset(); sha.Reset();
@ -63,6 +67,10 @@ public:
sha.Write(input.data(), input.size()); sha.Write(input.data(), input.size());
return *this; return *this;
} }
CHash160& Write(std::span<const unsigned char, 1> input) {
sha.Write(input[0]);
return *this;
}
CHash160& Reset() { CHash160& Reset() {
sha.Reset(); sha.Reset();
@ -107,6 +115,10 @@ public:
{ {
ctx.Write(UCharCast(src.data()), src.size()); ctx.Write(UCharCast(src.data()), src.size());
} }
void write(std::span<const std::byte, 1> src)
{
ctx.Write(*UCharCast(&src[0]));
}
/** Compute the double-SHA256 hash of all data written to this object. /** Compute the double-SHA256 hash of all data written to this object.
* *
@ -160,13 +172,18 @@ public:
m_source.read(dst); m_source.read(dst);
this->write(dst); this->write(dst);
} }
void read(std::span<std::byte, 1> dst)
{
m_source.read(dst);
this->write(std::span<const std::byte, 1>{dst});
}
void ignore(size_t num_bytes) void ignore(size_t num_bytes)
{ {
std::byte data[1024]; std::byte data[1024];
while (num_bytes > 0) { while (num_bytes > 0) {
size_t now = std::min<size_t>(num_bytes, 1024); size_t now = std::min<size_t>(num_bytes, 1024);
read({data, now}); read(std::span{data, now});
num_bytes -= now; num_bytes -= now;
} }
} }
@ -194,6 +211,11 @@ public:
m_source.write(src); m_source.write(src);
HashWriter::write(src); HashWriter::write(src);
} }
void write(std::span<const std::byte, 1> src)
{
m_source.write(src);
HashWriter::write(src);
}
template <typename T> template <typename T>
HashedSourceWriter& operator<<(const T& obj) HashedSourceWriter& operator<<(const T& obj)

View file

@ -63,56 +63,56 @@ concept ContainsSizeComputer = ContainsStream<T> &&
*/ */
template<typename Stream> 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<uint8_t, 1>{&obj, 1}));
} }
template<typename Stream> 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<uint16_t, 1>{&obj, 1}));
} }
template<typename Stream> 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<uint32_t, 1>{&obj, 1}));
} }
template<typename Stream> 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<uint32_t, 1>{&obj, 1}));
} }
template<typename Stream> 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<uint64_t, 1>{&obj, 1}));
} }
template<typename Stream> 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<uint8_t, 1>{&obj, 1}));
return obj; return obj;
} }
template<typename Stream> 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<uint16_t, 1>{&obj, 1}));
return le16toh_internal(obj); return le16toh_internal(obj);
} }
template<typename Stream> 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<uint32_t, 1>{&obj, 1}));
return le32toh_internal(obj); return le32toh_internal(obj);
} }
template<typename Stream> 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<uint32_t, 1>{&obj, 1}));
return be32toh_internal(obj); return be32toh_internal(obj);
} }
template<typename Stream> 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<uint64_t, 1>{&obj, 1}));
return le64toh_internal(obj); return le64toh_internal(obj);
} }
@ -319,7 +319,6 @@ template <typename Stream, ByteOrIntegral T> void Unserialize(Stream& s, T& a)
template <typename Stream, BasicByte B, int N> void Unserialize(Stream& s, B (&a)[N]) { s.read(MakeWritableByteSpan(a)); } template <typename Stream, BasicByte B, int N> void Unserialize(Stream& s, B (&a)[N]) { s.read(MakeWritableByteSpan(a)); }
template <typename Stream, BasicByte B, std::size_t N> void Unserialize(Stream& s, std::array<B, N>& a) { s.read(MakeWritableByteSpan(a)); } template <typename Stream, BasicByte B, std::size_t N> void Unserialize(Stream& s, std::array<B, N>& a) { s.read(MakeWritableByteSpan(a)); }
template <typename Stream, BasicByte B, std::size_t N> void Unserialize(Stream& s, std::span<B, N> span) { s.read(std::as_writable_bytes(span)); } template <typename Stream, BasicByte B, std::size_t N> void Unserialize(Stream& s, std::span<B, N> span) { s.read(std::as_writable_bytes(span)); }
template <typename Stream, BasicByte B> void Unserialize(Stream& s, std::span<B> span) { s.read(std::as_writable_bytes(span)); }
// clang-format on // clang-format on
@ -577,10 +576,10 @@ struct CustomUintFormatter
s.GetStream().seek(Bytes); s.GetStream().seek(Bytes);
} else if (BigEndian) { } 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}).template last<Bytes>());
} else { } else {
uint64_t raw = htole64_internal(v); 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<Bytes>());
} }
} }
@ -590,10 +589,10 @@ struct CustomUintFormatter
static_assert(std::numeric_limits<U>::max() >= MAX && std::numeric_limits<U>::min() <= 0, "Assigned type too small"); static_assert(std::numeric_limits<U>::max() >= MAX && std::numeric_limits<U>::min() <= 0, "Assigned type too small");
uint64_t raw = 0; uint64_t raw = 0;
if (BigEndian) { 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<Bytes>());
v = static_cast<I>(be64toh_internal(raw)); v = static_cast<I>(be64toh_internal(raw));
} else { } else {
s.read(std::as_writable_bytes(std::span{&raw, 1}).first(Bytes)); s.read(std::as_writable_bytes(std::span{&raw, 1}).first<Bytes>());
v = static_cast<I>(le64toh_internal(raw)); v = static_cast<I>(le64toh_internal(raw));
} }
} }
@ -1113,6 +1112,10 @@ public:
{ {
this->nSize += src.size(); this->nSize += src.size();
} }
void write(std::span<const std::byte, 1>)
{
this->nSize += 1;
}
/** Pretend _nSize bytes are written, without specifying them. */ /** Pretend _nSize bytes are written, without specifying them. */
void seek(size_t _nSize) void seek(size_t _nSize)
@ -1162,7 +1165,9 @@ public:
template <typename U> ParamsStream& operator<<(const U& obj) { ::Serialize(*this, obj); return *this; } template <typename U> ParamsStream& operator<<(const U& obj) { ::Serialize(*this, obj); return *this; }
template <typename U> ParamsStream& operator>>(U&& obj) { ::Unserialize(*this, obj); return *this; } template <typename U> ParamsStream& operator>>(U&& obj) { ::Unserialize(*this, obj); return *this; }
void write(std::span<const std::byte> src) { GetStream().write(src); } void write(std::span<const std::byte> src) { GetStream().write(src); }
void write(std::span<const std::byte, 1> src) { GetStream().write(src); }
void read(std::span<std::byte> dst) { GetStream().read(dst); } void read(std::span<std::byte> dst) { GetStream().read(dst); }
void read(std::span<std::byte, 1> dst) { GetStream().read(dst); }
void ignore(size_t num) { GetStream().ignore(num); } void ignore(size_t num) { GetStream().ignore(num); }
bool eof() const { return GetStream().eof(); } bool eof() const { return GetStream().eof(); }
size_t size() const { return GetStream().size(); } size_t size() const { return GetStream().size(); }

View file

@ -63,6 +63,12 @@ void AutoFile::read(std::span<std::byte> dst)
throw std::ios_base::failure(feof() ? "AutoFile::read: end of file" : "AutoFile::read: fread failed"); throw std::ios_base::failure(feof() ? "AutoFile::read: end of file" : "AutoFile::read: fread failed");
} }
} }
void AutoFile::read(std::span<std::byte, 1> 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) void AutoFile::ignore(size_t nSize)
{ {
@ -101,6 +107,25 @@ void AutoFile::write(std::span<const std::byte> src)
} }
} }
} }
void AutoFile::write(std::span<const std::byte, 1> 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() bool AutoFile::Commit()
{ {

View file

@ -83,6 +83,17 @@ public:
} }
nPos += src.size(); nPos += src.size();
} }
void write(std::span<const std::byte, 1> 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 <typename T> template <typename T>
VectorWriter& operator<<(const T& obj) VectorWriter& operator<<(const T& obj)
{ {
@ -254,6 +265,10 @@ public:
// Write to the end of the buffer // Write to the end of the buffer
vch.insert(vch.end(), src.begin(), src.end()); vch.insert(vch.end(), src.begin(), src.end());
} }
void write(std::span<const value_type, 1> src)
{
vch.push_back(src[0]);
}
template<typename T> template<typename T>
DataStream& operator<<(const T& obj) DataStream& operator<<(const T& obj)
@ -450,8 +465,10 @@ public:
// Stream subset // Stream subset
// //
void read(std::span<std::byte> dst); void read(std::span<std::byte> dst);
void read(std::span<std::byte, 1> dst);
void ignore(size_t nSize); void ignore(size_t nSize);
void write(std::span<const std::byte> src); void write(std::span<const std::byte> src);
void write(std::span<const std::byte, 1> src);
template <typename T> template <typename T>
AutoFile& operator<<(const T& obj) AutoFile& operator<<(const T& obj)

View file

@ -1079,7 +1079,7 @@ BOOST_AUTO_TEST_CASE(sha256d64)
in[j] = m_rng.randbits(8); in[j] = m_rng.randbits(8);
} }
for (int j = 0; j < i; ++j) { 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); SHA256D64(out2, in, i);
BOOST_CHECK(memcmp(out1, out2, 32 * i) == 0); BOOST_CHECK(memcmp(out1, out2, 32 * i) == 0);

View file

@ -29,14 +29,14 @@ FUZZ_TARGET(autofile)
[&] { [&] {
std::array<std::byte, 4096> arr{}; std::array<std::byte, 4096> arr{};
try { try {
auto_file.read({arr.data(), fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)}); auto_file.read(std::span{arr.data(), fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)});
} catch (const std::ios_base::failure&) { } catch (const std::ios_base::failure&) {
} }
}, },
[&] { [&] {
const std::array<std::byte, 4096> arr{}; const std::array<std::byte, 4096> arr{};
try { try {
auto_file.write({arr.data(), fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)}); auto_file.write(std::span{arr.data(), fuzzed_data_provider.ConsumeIntegralInRange<size_t>(0, 4096)});
} catch (const std::ios_base::failure&) { } catch (const std::ios_base::failure&) {
} }
}, },