Merge bitcoin/bitcoin#35025: refactor: use SpanReader in deserialization benchmarks

13c8df4d5a refactor: replace `DataStream` with `SpanReader` in block deserialization tests (Lőrinc)
2529f25555 refactor: use `SpanReader` in `PrevectorDeserialize` (Lőrinc)
b8eb6c2081 refactor: use `SpanReader` in `TestBlockAndIndex` (Lőrinc)
61d678a6e3 refactor: use `DataStream::clear` in `::read` and `::ignore` (Lőrinc)

Pull request description:

  ### Problem

  Block deserialization benches still read immutable fixture bytes through `DataStream`, which keeps around mutable stream semantics and old compaction-oriented setup that these call sites do not need anymore.

  ### Fix
  We first remove the stale `Rewind()` parameter and failure path, which reduces rewinding to a simple reset of the read position that `clear()` can reuse.

  We then route fully consumed `read()`  and `ignore()` paths through `clear()`, remove the leftover compaction references and dummy-byte workaround, and finally switch the block deserialization benchmark readers to `SpanReader`.

  `DeserializeBlockTest` can then deserialize directly from the fixture bytes without an untimed setup phase, while `CheckBlockTest` still keeps setup only to rebuild a fresh `CBlock` before  the timed `CheckBlock()` call.

  ### Context
  This follows the same direction as #34483 and is a follow-up to https://github.com/bitcoin/bitcoin/pull/34208.
  The modified benchmarks retain their previous timing.

  ### Benchmarks

  The affected benchmarks speeds don't seem to be affected by the changes.

  <details><summary>Before & After</summary>

  > Before:

  ```bash

  |               ns/op |                op/s |    err% |     total | benchmark
  |--------------------:|--------------------:|--------:|----------:|:----------
  |       37,591,891.96 |               26.60 |    1.0% |     11.07 | `BlockToJsonVerboseWrite`
  |          155,664.09 |            6,424.09 |    0.1% |     10.99 | `BlockToJsonVerbosity1`
  |       28,620,345.39 |               34.94 |    0.1% |     10.99 | `BlockToJsonVerbosity2`
  |       28,637,604.74 |               34.92 |    0.1% |     11.01 | `BlockToJsonVerbosity3`

  |            ns/block |             block/s |    err% |     total | benchmark
  |--------------------:|--------------------:|--------:|----------:|:----------
  |          530,167.00 |            1,886.20 |    4.7% |      0.01 | `CheckBlockTest`
  |        1,439,417.00 |              694.73 |    0.7% |      0.02 | `DeserializeBlockTest`

  |               ns/op |                op/s |    err% |     total | benchmark
  |--------------------:|--------------------:|--------:|----------:|:----------
  |              269.95 |        3,704,375.43 |    0.4% |     11.01 | `PrevectorDeserializeNontrivial`
  |               14.90 |       67,114,436.52 |    0.0% |     10.88 | `PrevectorDeserializeTrivial`
  ```

  > After:

  ```bash
  |               ns/op |                op/s |    err% |     total | benchmark
  |--------------------:|--------------------:|--------:|----------:|:----------
  |       37,114,824.07 |               26.94 |    1.8% |     10.89 | `BlockToJsonVerboseWrite`
  |          154,881.99 |            6,456.53 |    0.2% |     10.99 | `BlockToJsonVerbosity1`
  |       28,546,697.37 |               35.03 |    0.2% |     10.98 | `BlockToJsonVerbosity2`
  |       28,547,328.27 |               35.03 |    0.3% |     11.02 | `BlockToJsonVerbosity3`

  |            ns/block |             block/s |    err% |     total | benchmark
  |--------------------:|--------------------:|--------:|----------:|:----------
  |          522,750.00 |            1,912.96 |    4.7% |      0.01 | `CheckBlockTest`
  |        1,404,510.54 |              711.99 |    0.1% |     11.00 | `DeserializeBlockTest`

  |               ns/op |                op/s |    err% |     total | benchmark
  |--------------------:|--------------------:|--------:|----------:|:----------
  |              273.52 |        3,655,991.66 |    0.4% |     11.00 | `PrevectorDeserializeNontrivial`
  |               14.31 |       69,863,193.52 |    1.4% |     11.03 | `PrevectorDeserializeTrivial`
  ```

  </details>

ACKs for top commit:
  maflcko:
    review ACK 13c8df4d5a 🐠
  sedited:
    Re-ACK 13c8df4d5a

Tree-SHA512: b469874908c694b6b7f45e686519bdce0c0f4da2ca56b3f7f9897c7f27bb19a787f9821466995f15414343d508f15616b24b7fd8f0fa389ade8698c8f190b669
This commit is contained in:
merge-script
2026-04-21 13:51:39 +02:00
4 changed files with 21 additions and 51 deletions

View File

@@ -4,22 +4,14 @@
#include <bench/bench.h>
#include <bench/data/block413567.raw.h>
#include <chainparams.h>
#include <common/args.h>
#include <consensus/validation.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <serialize.h>
#include <span.h>
#include <streams.h>
#include <util/chaintype.h>
#include <validation.h>
#include <cassert>
#include <cstddef>
#include <memory>
#include <optional>
#include <vector>
// These are the two major time-sinks which happen after we have fully received
// a block off the wire, but before we can relay the block on to peers using
@@ -27,27 +19,29 @@
static void DeserializeBlockTest(benchmark::Bench& bench)
{
DataStream stream;
bench.unit("block").epochIterations(1)
.setup([&] { stream = DataStream{benchmark::data::block413567}; })
.run([&] { CBlock block; stream >> TX_WITH_WITNESS(block); });
const auto block_data{benchmark::data::block413567};
bench.unit("block").run([&] {
CBlock block;
SpanReader{block_data} >> TX_WITH_WITNESS(block);
assert(block.vtx.size() == 1557);
});
}
static void CheckBlockTest(benchmark::Bench& bench)
{
ArgsManager bench_args;
const auto chainParams = CreateChainParams(bench_args, ChainType::MAIN);
const auto& chain_params{CChainParams::Main()};
const auto block_data{benchmark::data::block413567};
CBlock block;
bench.unit("block").epochIterations(1)
.setup([&] {
block = CBlock{};
DataStream stream{benchmark::data::block413567};
stream >> TX_WITH_WITNESS(block);
SpanReader{block_data} >> TX_WITH_WITNESS(block);
assert(block.vtx.size() == 1557);
})
.run([&] {
BlockValidationState validationState;
bool checked = CheckBlock(block, validationState, chainParams->GetConsensus());
const bool checked{CheckBlock(block, validationState, chain_params->GetConsensus())};
assert(checked);
});
}

View File

@@ -66,22 +66,22 @@ static void PrevectorResize(benchmark::Bench& bench)
template <typename T>
static void PrevectorDeserialize(benchmark::Bench& bench)
{
DataStream s0{};
DataStream data{};
prevector<CScriptBase::STATIC_SIZE, T> t0;
t0.resize(CScriptBase::STATIC_SIZE);
for (auto x = 0; x < 900; ++x) {
s0 << t0;
data << t0;
}
t0.resize(100);
for (auto x = 0; x < 101; ++x) {
s0 << t0;
for (auto x = 0; x < 100; ++x) {
data << t0;
}
bench.batch(1000).run([&] {
SpanReader s0{data};
prevector<CScriptBase::STATIC_SIZE, T> t1;
for (auto x = 0; x < 1000; ++x) {
s0 >> t1;
}
s0.Rewind();
});
}

View File

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

View File

@@ -206,27 +206,6 @@ public:
value_type* data() { return vch.data() + m_read_pos; }
const value_type* data() const { return vch.data() + m_read_pos; }
inline void Compact()
{
vch.erase(vch.begin(), vch.begin() + m_read_pos);
m_read_pos = 0;
}
bool Rewind(std::optional<size_type> n = std::nullopt)
{
// Total rewind if no size is passed
if (!n) {
m_read_pos = 0;
return true;
}
// Rewind by n characters if the buffer hasn't been compacted yet
if (*n > m_read_pos)
return false;
m_read_pos -= *n;
return true;
}
//
// Stream subset
//
@@ -243,8 +222,8 @@ public:
}
memcpy(dst.data(), &vch[m_read_pos], dst.size());
if (next_read_pos.value() == vch.size()) {
m_read_pos = 0;
vch.clear();
// If fully consumed, reset to empty state.
clear();
return;
}
m_read_pos = next_read_pos.value();
@@ -258,8 +237,8 @@ public:
throw std::ios_base::failure("DataStream::ignore(): end of data");
}
if (next_read_pos.value() == vch.size()) {
m_read_pos = 0;
vch.clear();
// If all bytes are ignored, reset to empty state.
clear();
return;
}
m_read_pos = next_read_pos.value();