optimization: bulk serialization writes in WriteBlockUndo and WriteBlock
Similarly to the serialization reads optimization, buffered writes will enable batched XOR calculations. This is especially beneficial since the current implementation requires copying the write input's `std::span` to perform obfuscation. Batching allows us to apply XOR operations on the internal buffer instead, reducing unnecessary data copying and improving performance. ------ > macOS Sequoia 15.3.1 > C++ compiler .......................... Clang 19.1.7 > cmake -B build -DBUILD_BENCH=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ && cmake --build build -j$(nproc) && build/bin/bench_bitcoin -filter='WriteBlockBench' -min-time=10000 Before: | ns/op | op/s | err% | total | benchmark |--------------------:|--------------------:|--------:|----------:|:---------- | 5,149,564.31 | 194.19 | 0.8% | 10.95 | `WriteBlockBench` After: | ns/op | op/s | err% | total | benchmark |--------------------:|--------------------:|--------:|----------:|:---------- | 2,990,564.63 | 334.39 | 1.5% | 11.27 | `WriteBlockBench` ------ > Ubuntu 24.04.2 LTS > C++ compiler .......................... GNU 13.3.0 > cmake -B build -DBUILD_BENCH=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ && cmake --build build -j$(nproc) && build/bin/bench_bitcoin -filter='WriteBlockBench' -min-time=20000 Before: | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark |--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------- | 5,152,973.58 | 194.06 | 2.2% | 19,350,886.41 | 8,784,539.75 | 2.203 | 3,079,335.21 | 0.4% | 23.18 | `WriteBlockBench` After: | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark |--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------- | 4,145,681.13 | 241.21 | 4.0% | 15,337,596.85 | 5,732,186.47 | 2.676 | 2,239,662.64 | 0.1% | 23.94 | `WriteBlockBench` Co-authored-by: Ryan Ofsky <ryan@ofsky.org> Co-authored-by: Cory Fields <cory-nospam-@coryfields.com>
This commit is contained in:
1 parent
520965e293
commit
8d801e3efb
4 files changed
+155
-17
No files matched your search
@@ -445,6 +445,9 @@ public:
|
||||
/** Wrapper around TruncateFile(). */
|
||||
bool Truncate(unsigned size);
|
||||
|
||||
//! Write a mutable buffer more efficiently than write(), obfuscating the buffer in-place.
|
||||
void write_buffer(std::span<std::byte> src);
|
||||
|
||||
//
|
||||
// Stream subset
|
||||
//
|
||||
@@ -658,4 +661,45 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper that buffers writes to an underlying stream.
|
||||
* Requires underlying stream to support write_buffer() method
|
||||
* for efficient buffer flushing and obfuscation.
|
||||
*/
|
||||
template <typename S>
|
||||
class BufferedWriter
|
||||
{
|
||||
S& m_dst;
|
||||
DataBuffer m_buf;
|
||||
size_t m_buf_pos{0};
|
||||
|
||||
public:
|
||||
explicit BufferedWriter(S& stream LIFETIMEBOUND, size_t size = 1 << 16) : m_dst{stream}, m_buf(size) {}
|
||||
|
||||
~BufferedWriter() { flush(); }
|
||||
|
||||
void flush()
|
||||
{
|
||||
if (m_buf_pos) m_dst.write_buffer(std::span{m_buf}.first(m_buf_pos));
|
||||
m_buf_pos = 0;
|
||||
}
|
||||
|
||||
void write(std::span<const std::byte> src)
|
||||
{
|
||||
while (const auto available{std::min(src.size(), m_buf.size() - m_buf_pos)}) {
|
||||
std::copy_n(src.begin(), available, m_buf.begin() + m_buf_pos);
|
||||
m_buf_pos += available;
|
||||
if (m_buf_pos == m_buf.size()) flush();
|
||||
src = src.subspan(available);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
BufferedWriter& operator<<(const T& obj)
|
||||
{
|
||||
Serialize(*this, obj);
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // BITCOIN_STREAMS_H
|
||||
Reference in new issue
Block a user