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`
This commit is contained in:
Lőrinc 2025-02-14 00:53:11 +01:00
parent 028c006541
commit c02600b8e1
4 changed files with 49 additions and 1 deletions

View File

@ -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 <typename T>
HashedSourceWriter& operator<<(const T& obj)

View File

@ -53,7 +53,7 @@ constexpr deserialize_type deserialize {};
*/
template<typename Stream> inline void ser_writedata8(Stream &s, uint8_t obj)
{
s.write(AsBytes(Span{&obj, 1}));
s.write(std::byte{obj});
}
template<typename Stream> 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 <typename U> ParamsStream& operator<<(const U& obj) { ::Serialize(*this, obj); return *this; }
template <typename U> ParamsStream& operator>>(U&& obj) { ::Unserialize(*this, obj); return *this; }
void write(Span<const std::byte> src) { GetStream().write(src); }
void write(std::byte src) { GetStream().write(src); }
void read(Span<std::byte> dst) { GetStream().read(dst); }
void ignore(size_t num) { GetStream().ignore(num); }
bool eof() const { return GetStream().eof(); }

View File

@ -111,6 +111,24 @@ void AutoFile::write_large(Span<std::byte> 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);

View File

@ -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<unsigned char>(val);
} else {
vchData.push_back(static_cast<unsigned char>(val));
}
nPos += 1;
}
template <typename T>
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<typename T>
DataStream& operator<<(const T& obj)
@ -427,6 +442,7 @@ public:
void ignore(size_t nSize);
void write(Span<const std::byte> src);
void write_large(Span<std::byte> src); // Note that src will be mutated
void write(std::byte src);
template <typename T>
AutoFile& operator<<(const T& obj)