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:
Lőrinc
2025-03-26 12:54:07 +01:00
parent 520965e293
commit 8d801e3efb
4 changed files with 155 additions and 17 deletions

View File

@@ -942,11 +942,12 @@ bool BlockManager::WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationSt
{
// Open history file to append
AutoFile fileout{OpenUndoFile(pos)};
if (fileout.IsNull()) {
AutoFile file{OpenUndoFile(pos)};
if (file.IsNull()) {
LogError("OpenUndoFile failed for %s while writing block undo", pos.ToString());
return FatalError(m_opts.notifications, state, _("Failed to write undo data."));
}
BufferedWriter fileout{file};
// Write index header
fileout << GetParams().MessageStart() << blockundo_size;
@@ -959,11 +960,7 @@ bool BlockManager::WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationSt
fileout << blockundo << hasher.GetHash();
}
// Make sure `AutoFile` goes out of scope before we call `FlushUndoFile`
if (fileout.fclose()) {
LogError("WriteBlockUndo: fclose failed");
return false;
}
fileout.flush(); // Make sure `AutoFile`/`BufferedWriter` go out of scope before we call `FlushUndoFile`
}
// rev files are written in block height order, whereas blk files are written as blocks come in (often out of order)
@@ -1090,12 +1087,13 @@ FlatFilePos BlockManager::WriteBlock(const CBlock& block, int nHeight)
LogError("FindNextBlockPos failed for %s while writing block", pos.ToString());
return FlatFilePos();
}
AutoFile fileout{OpenBlockFile(pos, /*fReadOnly=*/false)};
if (fileout.IsNull()) {
AutoFile file{OpenBlockFile(pos, /*fReadOnly=*/false)};
if (file.IsNull()) {
LogError("OpenBlockFile failed for %s while writing block", pos.ToString());
m_opts.notifications.fatalError(_("Failed to write block."));
return FlatFilePos();
}
BufferedWriter fileout{file};
// Write index header
fileout << GetParams().MessageStart() << block_size;