optimization: Bulk serialization writes in SaveBlockUndo and SaveBlock

Similarly to the serialization reads, buffered writes will enable batched xor calculations - especially since currently we need to copy the write inputs Span to do the obfuscation on it, batching enables doing the xor on the internal buffer instead.

> cmake -B build -DBUILD_BENCH=ON -DCMAKE_BUILD_TYPE=Release && cmake --build build -j$(nproc) && build/src/bench/bench_bitcoin -filter='SaveBlockBench' -min-time=10000

> C++ compiler .......................... AppleClang 16.0.0.16000026

Before:
|               ns/op |                op/s |    err% |     total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
|        5,267,613.94 |              189.84 |    1.0% |     11.05 | `SaveBlockBench`

After:
|               ns/op |                op/s |    err% |     total | benchmark
|--------------------:|--------------------:|--------:|----------:|:----------
|        1,767,367.40 |              565.81 |    1.6% |     10.86 | `SaveBlockBench`

> C++ compiler .......................... GNU 13.3.0

Before:
|               ns/op |                op/s |    err% |          ins/op |          cyc/op |    IPC |         bra/op |   miss% |     total | benchmark
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|        4,128,530.90 |              242.22 |    3.8% |   19,358,001.33 |    8,601,983.31 |  2.250 |   3,079,334.76 |    0.4% |     10.64 | `SaveBlockBench`

After:
|               ns/op |                op/s |    err% |          ins/op |          cyc/op |    IPC |         bra/op |   miss% |     total | benchmark
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|        3,130,556.05 |              319.43 |    4.7% |   17,305,378.56 |    6,457,946.37 |  2.680 |   2,579,854.87 |    0.3% |     10.83 | `SaveBlockBench`

Co-authored-by: Cory Fields <cory-nospam-@coryfields.com>
This commit is contained in:
Lőrinc 2025-01-26 17:22:47 +01:00
parent e18c96a3fe
commit 23ed684c6a

View File

@ -974,16 +974,16 @@ bool BlockManager::WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationSt
}
// Write index header
fileout << GetParams().MessageStart() << blockundo_size;
// Write undo data
fileout.write_large(DataStream(BLOCK_SERIALIZATION_HEADER_SIZE) << GetParams().MessageStart() << blockundo_size);
pos.nPos += BLOCK_SERIALIZATION_HEADER_SIZE;
fileout << blockundo;
{
// Calculate checksum
HashWriter hasher{};
hasher << block.pprev->GetBlockHash() << blockundo;
// Calculate & write checksum
HashWriter hasher{};
hasher << block.pprev->GetBlockHash();
hasher << blockundo;
fileout << hasher.GetHash();
// Write undo data & checksum
fileout.write_large(DataStream(blockundo_size + sizeof(uint256)) << blockundo << hasher.GetHash());
}
// rev files are written in block height order, whereas blk files are written as blocks come in (often out of order)
// we want to flush the rev (undo) file once we've written the last block, which is indicated by the last height
@ -1135,10 +1135,11 @@ FlatFilePos BlockManager::WriteBlock(const CBlock& block, int nHeight)
}
// Write index header
fileout << GetParams().MessageStart() << block_size;
// Write block
fileout.write_large(DataStream(BLOCK_SERIALIZATION_HEADER_SIZE) << GetParams().MessageStart() << block_size);
pos.nPos += BLOCK_SERIALIZATION_HEADER_SIZE;
fileout << TX_WITH_WITNESS(block);
// Write block
fileout.write_large(DataStream(block_size) << TX_WITH_WITNESS(block));
return pos;
}