dbwrapper: reuse scratch `DataStream` buffers #35156

pull l0rinc wants to merge 4 commits into bitcoin:master from l0rinc:l0rinc/ScopedDataStreamUsage changing 5 files +87 −26
  1. l0rinc commented at 9:29 PM on April 24, 2026: contributor

    Problem

    CDBIterator::GetValue() cannot use SpanReader the same way as ::GetKey() because values are deobfuscated in place before deserialization, so it still needs an owning mutable buffer. However, the current path allocates a fresh DataStream for every value read.

    The same local-stream pattern also exists in CDBIterator::Seek(), while CDBBatch already owns reusable key/value buffers but still manually reserves and clears them on every Write() and Erase() call.

    Fix

    Add ScopedDataStreamUsage, a small RAII helper for caller-owned scratch streams. It asserts that the stream is empty on entry (making accidental re-entry or concurrent use of the same scratch stream fail fast), and clears it on scope exit.

    Use it to guard the reusable scratch streams in CDBBatch::Write(), ::Erase() and CDBIterator::Seek(), ::GetValue(). The const read-side CDBWrapper helpers stay unchanged, since they can be called concurrently on the same wrapper and should keep using local streams.

    The production changes are preceded by tests covering repeated reuse on the same owning objects, including a failed iterator value decode followed by a successful read from the same iterator entry.

    Context

    Follow-up to #35128, #34483 and #35025.

    Reproducer

    gettxoutsetinfo gets an additional ~6% speedup on top of the previous iterator-key optimization:

    <details><summary>2026-04-24 | gettxoutsetinfo | i9-ssd | x86_64 | Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz | 16 cores | 62Gi RAM | xfs | SSD</summary>

    COMMITS="2d5ab09f0dca4bfec0b365f5f431def2c0c9d70f 9e5fd595ae8ebeec74678c31a547fbc14f87bf89"; \
    BASE_DIR="/mnt/my_storage"; DATA_DIR="$BASE_DIR/BitcoinData"; LOG_DIR="$BASE_DIR/logs"; \
    mkdir -p "$LOG_DIR" && \
    (echo ""; for c in $COMMITS; do git cat-file -e "$c^{commit}" 2>/dev/null || git fetch -q origin "$c" || exit 1; git log -1 --pretty='%h %s' "$c" || exit 1; done) && \
    (echo "" && echo "$(date -I) | gettxoutsetinfo | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | $(df -T $BASE_DIR | awk 'NR==2{print $2}') | $(lsblk -no ROTA $(df --output=source $BASE_DIR | tail -1) | grep -q 1 && echo HDD || echo SSD)"; echo "") && \
    hyperfine \
      --sort command \
      --runs 3 \
      --export-json "$BASE_DIR/gettxoutsetinfo-$(sed -E 's/([a-f0-9]{8})[a-f0-9]* ?/\1-/g;s/-$//'<<<"$COMMITS")-$(date +%s).json" \
      --parameter-list COMMIT ${COMMITS// /,} \
      --prepare "killall -9 bitcoind 2>/dev/null || true; rm -f $DATA_DIR/debug.log; git clean -fxd && git reset --hard {COMMIT} && \
        cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && ninja -C build bitcoind bitcoin-cli -j$(nproc) && \
        ./build/bin/bitcoind -datadir=$DATA_DIR -connect=0 -listen=0 -dnsseed=0 -coinstatsindex=0 -txindex=0 -blockfilterindex=0 -daemon -printtoconsole=0; \
        ./build/bin/bitcoin-cli -datadir=$DATA_DIR -rpcwait getblockcount >/dev/null" \
      --conclude "./build/bin/bitcoin-cli -datadir=$DATA_DIR stop 2>/dev/null || true; killall bitcoind 2>/dev/null || true; sleep 10; \
        grep -q 'Done loading' $DATA_DIR/debug.log && grep 'Bitcoin Core version' $DATA_DIR/debug.log | grep -q \"\$(git rev-parse --short=12 {COMMIT})\"; \
        cp $DATA_DIR/debug.log $LOG_DIR/gettxoutsetinfo-{COMMIT}-$(date +%s).log" \
      "./build/bin/bitcoin-cli -datadir=$DATA_DIR -rpcclienttimeout=0 -named gettxoutsetinfo hash_type='none' use_index='false' >/dev/null"
    
    2d5ab09f0d Merge bitcoin/bitcoin#35124: bench: fix benchmark fixtures and setup checks
    cb63e158d9 walletdb: reuse batch scratch streams
    
    2026-04-24 | gettxoutsetinfo | i9-ssd | x86_64 | Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz | 16 cores | 62Gi RAM | xfs | SSD
    
    Benchmark 1: ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named gettxoutsetinfo hash_type='none' use_index='false' >/dev/null (COMMIT = 2d5ab09f0dca4bfec0b365f5f431def2c0c9d70f)
      Time (mean ± σ):     60.063 s ±  1.623 s    [User: 0.001 s, System: 0.002 s]
      Range (min … max):   59.020 s … 61.933 s    3 runs
    
    Benchmark 2: ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named gettxoutsetinfo hash_type='none' use_index='false' >/dev/null (COMMIT = cb63e158d9ae8276dbe54bba0b0cf8f35378ec71)
      Time (mean ± σ):     56.853 s ±  0.179 s    [User: 0.002 s, System: 0.001 s]
      Range (min … max):   56.675 s … 57.033 s    3 runs
    
    Relative speed comparison
            1.06 ±  0.03  ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named gettxoutsetinfo hash_type='none' use_index='false' >/dev/null (COMMIT = 2d5ab09f0dca4bfec0b365f5f431def2c0c9d70f)
            1.00          ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named gettxoutsetinfo hash_type='none' use_index='false' >/dev/null (COMMIT = cb63e158d9ae8276dbe54bba0b0cf8f35378ec71)
    

    </details>

  2. DrahtBot commented at 9:29 PM on April 24, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK andrewtoth
    Concept ACK optout21, jmoik

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #34132 (coins: drop error catcher, centralize fatal read handling by l0rinc)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  3. in src/dbwrapper.h:125 in 4e9fd50b38
     120 | @@ -121,6 +121,8 @@ class CDBIterator
     121 |  private:
     122 |      const CDBWrapper &parent;
     123 |      const std::unique_ptr<IteratorImpl> m_impl_iter;
     124 | +    DataStream m_seek_key{};
     125 | +    DataStream m_value{};
    


    andrewtoth commented at 9:38 PM on April 24, 2026:

    What is the purpose of having a separate member for value and key here? Seek and GetValue cannot be called concurrently, and the ScopedDataStreamUsage will ensure a single DataStream will always be cleared before either method is called.


    l0rinc commented at 5:47 AM on April 25, 2026:

    Was a bit hesitant to do that, but the ScopedDataStreamUsage assertion guard should indeed immediately catch any accidental nested/concurrent violation. Changed to use one m_scratch member now which backs both Seek() and GetValue(), thanks, added you as coauthor.

  4. bitcoin deleted a comment on Apr 24, 2026
  5. in src/wallet/db.h:113 in 9e5fd595ae
     123 | -        ssKey << key;
     124 | +        ScopedDataStreamUsage scoped_key{m_key};
     125 | +        m_key << key;
     126 |  
     127 | -        return HasKey(std::move(ssKey));
     128 | +        return HasKey(std::move(m_key));
    


    andrewtoth commented at 10:29 PM on April 24, 2026:

    I'm not sure the wallet db code should be touched here. These methods are all moving m_key and m_value, which can steal the underlying heap buffer. If they do, then that negates the purpose of keeping the DataStream members and not reserving here.

    (And if they don't, then that's an implementation detail of SQLiteBatch which we can't depend on)


    l0rinc commented at 6:16 AM on April 25, 2026:

    Good point, dropped.

  6. l0rinc force-pushed on Apr 25, 2026
  7. l0rinc force-pushed on Apr 25, 2026
  8. l0rinc commented at 6:20 AM on April 25, 2026: contributor

    Updated the branch based on the review feedback:

    • Dropped the walletdb changes from this PR - should be a separate change if we still want to pursue it.
    • Merged the iterator seek/value scratch streams into one m_scratch, since Seek() and GetValue() cannot overlap on the same iterator and the guard catches accidental nested/concurrent reuse.
    • Now that the remaining change is small enough I snuck in a rename for the persistent scratch streams to use m_*_scratch names instead of the old local ss* naming (I don't even know what ss stands for, is it "string stream"?).
    • Simplified the commit messages to match the narrower scope.
  9. DrahtBot added the label CI failed on Apr 25, 2026
  10. l0rinc renamed this:
    dbwrapper/walletdb: reuse scratch `DataStream` buffers
    dbwrapper: reuse scratch `DataStream` buffers
    on Apr 25, 2026
  11. DrahtBot removed the label CI failed on Apr 25, 2026
  12. in src/dbwrapper.h:100 in 60d8c5c887 outdated
      94 | @@ -96,22 +95,18 @@ class CDBBatch
      95 |      template <typename K, typename V>
      96 |      void Write(const K& key, const V& value)
      97 |      {
      98 | -        ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
      99 | -        ssValue.reserve(DBWRAPPER_PREALLOC_VALUE_SIZE);
    


    andrewtoth commented at 12:52 PM on April 25, 2026:

    Is there value in removing the reserves here? There could still be batches of one element which this helps.

    Similar thought for the iterator. An iterator of one could also benefit from reserving.


    l0rinc commented at 2:34 PM on April 25, 2026:

    I'll investigate how likely that is, I assumed that's not how we're using them.


    andrewtoth commented at 2:24 PM on April 26, 2026:

    If in doubt, maybe we should just keep these reserve calls. They are harmless and could result in a regression if removed.


    l0rinc commented at 4:19 PM on April 26, 2026:

    Yeah, that's fair, I'll pre-reserve in the constructor - and we can investigate if that's still needed in a possible follow-up.

  13. l0rinc force-pushed on Apr 26, 2026
  14. in src/test/dbwrapper_tests.cpp:231 in e7206f2252
     226 | +        // A failed value decode must not leave the iterator's scratch stream dirty.
     227 | +        std::pair<uint256, uint8_t> value_too_large;
     228 | +        BOOST_CHECK(!it->GetValue(value_too_large));
     229 | +
     230 | +        BOOST_REQUIRE(it->GetValue(val_res));
     231 | +        BOOST_CHECK_EQUAL(key_res, key);
    


    andrewtoth commented at 12:53 AM on April 27, 2026:

    nit: this check can be moved up right below BOOST_REQUIRE(it->GetKey(key_res));.

    Same for below. The check should go right after getting key, not after getting value.


    l0rinc commented at 8:42 AM on April 28, 2026:

    I grouped by setup/test (as it was before), you're suggesting setup/test/setup/test. I'm fine with both, applied, pushed, thanks.

  15. andrewtoth approved
  16. andrewtoth commented at 1:11 AM on April 27, 2026: contributor

    ACK 5c3e0202df9ee53d80fea45e1805992d93a61390

    I get a nice ~10% speedup of gettxoutsetinfo - 46.897s on master and 42.652s on this branch.

    I'm not really sure we need the ScopedDataStreamUsage on the serialization code (deserialization makes sense), or that we need to touch CDBBatch at all. But, the empty assertion and not having to manually call clear() is an improvement I suppose.

    nit is non-blocking.

  17. l0rinc commented at 8:34 AM on April 28, 2026: contributor

    Compared gettxoutsetinfo and scantxoutset to v29, v30 and the two recent changes:

    <img width="1784" height="751" alt="image" src="https://github.com/user-attachments/assets/c227e457-b86c-4116-b1df-221e3551f339" />


    <details><summary>2026-04-27 | gettxoutsetinfo | umbrel | x86_64 | Intel(R) N150 | 4 cores | 15Gi RAM | ext4 | SSD</summary>

    COMMITS="99003bed87333f1be51bf3070235591b3a72f007 4d7d5f6b79d4c11c47e7a828d81296918fd11d4d 5de2f97a0521fe75b47f9840141c4ace74656de9 5c3e0202df9ee53d80fea45e1805992d93a61390"; \
    BASE_DIR="/mnt/my_storage"; DATA_DIR="$BASE_DIR/BitcoinData"; LOG_DIR="$BASE_DIR/logs"; \
    mkdir -p "$LOG_DIR" && \
    (echo ""; for c in $COMMITS; do git cat-file -e "$c^{commit}" 2>/dev/null || git fetch -q origin "$c" || exit 1; git log -1 --pretty='%h %s' "$c" || exit 1; done) && \
    (echo "" && echo "$(date -I) | gettxoutsetinfo | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | $(df -T $BASE_DIR | awk 'NR==2{print $2}') | $(lsblk -no ROTA $(df --output=source $BASE_DIR | tail -1) | grep -q 1 && echo HDD || echo SSD)"; echo "") && \
    hyperfine \
      --sort command \
      --runs 3 \
      --export-json "$BASE_DIR/gettxoutsetinfo-$(sed -E 's/([a-f0-9]{8})[a-f0-9]* ?/\1-/g;s/-$//'<<<"$COMMITS")-$(date +%s).json" \
      --parameter-list COMMIT ${COMMITS// /,} \
      --prepare "killall -9 bitcoind 2>/dev/null || true; rm -f $DATA_DIR/debug.log; git clean -fxd && git reset --hard {COMMIT} && \
        cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && ninja -C build bitcoind bitcoin-cli -j$(nproc) && \
        ./build/bin/bitcoind -datadir=$DATA_DIR -connect=0 -listen=0 -dnsseed=0 -coinstatsindex=0 -txindex=0 -blockfilterindex=0 -daemon -printtoconsole=0; \
        ./build/bin/bitcoin-cli -datadir=$DATA_DIR -rpcwait getblockcount >/dev/null" \
      --conclude "./build/bin/bitcoin-cli -datadir=$DATA_DIR stop 2>/dev/null || true; killall bitcoind 2>/dev/null || true; sleep 10; \
        grep -q 'Done loading' $DATA_DIR/debug.log && grep 'Bitcoin Core version' $DATA_DIR/debug.log | grep -q \"\$(git rev-parse --short=12 {COMMIT})\"; \
        cp $DATA_DIR/debug.log $LOG_DIR/gettxoutsetinfo-{COMMIT}-$(date +%s).log" \
      "./build/bin/bitcoin-cli -datadir=$DATA_DIR -rpcclienttimeout=0 -named gettxoutsetinfo hash_type='none' use_index='false' >/dev/null"
    
    99003bed87 Merge bitcoin/bitcoin#34536: [29.x] Finalise v29.3
    4d7d5f6b79 Merge bitcoin/bitcoin#34229: [30.2] Backports & Final
    5de2f97a05 dbwrapper: use `SpanReader` for iterator keys
    5c3e0202df dbwrapper: reuse iterator scratch stream
    
    Benchmark 1: ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named gettxoutsetinfo hash_type='none' use_index='false' >/dev/null (COMMIT = 99003bed87333f1be51bf3070235591b3a72f007)
      Time (mean ± σ):     65.509 s ±  8.208 s    [User: 0.003 s, System: 0.001 s]
      Range (min … max):   60.763 s … 74.988 s    3 runs
    
    Benchmark 2: ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named gettxoutsetinfo hash_type='none' use_index='false' >/dev/null (COMMIT = 4d7d5f6b79d4c11c47e7a828d81296918fd11d4d)
      Time (mean ± σ):     56.836 s ±  1.934 s    [User: 0.001 s, System: 0.001 s]
      Range (min … max):   55.319 s … 59.014 s    3 runs
    
    Benchmark 3: ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named gettxoutsetinfo hash_type='none' use_index='false' >/dev/null (COMMIT = 5de2f97a0521fe75b47f9840141c4ace74656de9)
      Time (mean ± σ):     49.609 s ±  1.410 s    [User: 0.003 s, System: 0.001 s]
      Range (min … max):   48.496 s … 51.194 s    3 runs
    
    Benchmark 4: ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named gettxoutsetinfo hash_type='none' use_index='false' >/dev/null (COMMIT = 5c3e0202df9ee53d80fea45e1805992d93a61390)
      Time (mean ± σ):     48.163 s ±  1.224 s    [User: 0.001 s, System: 0.002 s]
      Range (min … max):   47.392 s … 49.575 s    3 runs
    
    Relative speed comparison
            1.36 ±  0.17  ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named gettxoutsetinfo hash_type='none' use_index='false' >/dev/null (COMMIT = 99003bed87333f1be51bf3070235591b3a72f007)
            1.18 ±  0.05  ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named gettxoutsetinfo hash_type='none' use_index='false' >/dev/null (COMMIT = 4d7d5f6b79d4c11c47e7a828d81296918fd11d4d)
            1.03 ±  0.04  ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named gettxoutsetinfo hash_type='none' use_index='false' >/dev/null (COMMIT = 5de2f97a0521fe75b47f9840141c4ace74656de9)
            1.00          ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named gettxoutsetinfo hash_type='none' use_index='false' >/dev/null (COMMIT = 5c3e0202df9ee53d80fea45e1805992d93a61390)
    

    </details>


    <details><summary>2026-04-27 | scantxoutset | umbrel | x86_64 | Intel(R) N150 | 4 cores | 15Gi RAM | ext4 | SSD</summary>

    COMMITS="99003bed87333f1be51bf3070235591b3a72f007 4d7d5f6b79d4c11c47e7a828d81296918fd11d4d 5de2f97a0521fe75b47f9840141c4ace74656de9 5c3e0202df9ee53d80fea45e1805992d93a61390"; \
    BASE_DIR="/mnt/my_storage"; DATA_DIR="$BASE_DIR/BitcoinData"; LOG_DIR="$BASE_DIR/logs"; \
    mkdir -p "$LOG_DIR" && \
    (echo ""; for c in $COMMITS; do git cat-file -e "$c^{commit}" 2>/dev/null || git fetch -q origin "$c" || exit 1; git log -1 --pretty='%h %s' "$c" || exit 1; done) && \
    (echo "" && echo "$(date -I) | scantxoutset | $(hostname) | $(uname -m) | $(lscpu | grep 'Model name' | head -1 | cut -d: -f2 | xargs) | $(nproc) cores | $(free -h | awk '/^Mem:/{print $2}') RAM | $(df -T $BASE_DIR | awk 'NR==2{print $2}') | $(lsblk -no ROTA $(df --output=source $BASE_DIR | tail -1) | grep -q 1 && echo HDD || echo SSD)"; echo "") && \
    hyperfine \
      --sort command \
      --runs 10 \
      --export-json "$BASE_DIR/scantxoutset-$(sed -E 's/([a-f0-9]{8})[a-f0-9]* ?/\1-/g;s/-$//'<<<"$COMMITS")-$(date +%s).json" \
      --parameter-list COMMIT ${COMMITS// /,} \
      --prepare "killall -9 bitcoind 2>/dev/null || true; rm -f $DATA_DIR/debug.log; git clean -fxd && git reset --hard {COMMIT} && \
        cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j$(nproc) --target bitcoind bitcoin-cli && \
        ./build/bin/bitcoind -datadir=$DATA_DIR -connect=0 -listen=0 -dnsseed=0 -coinstatsindex=0 -txindex=0 -blockfilterindex=0 -daemon -printtoconsole=0; \
        ./build/bin/bitcoin-cli -datadir=$DATA_DIR -rpcclienttimeout=0 -rpcwait getblockcount >/dev/null" \
      --conclude "./build/bin/bitcoin-cli -datadir=$DATA_DIR stop 2>/dev/null || true; killall bitcoind 2>/dev/null || true; sleep 10; \
        grep -q 'Done loading' $DATA_DIR/debug.log; \
        cp $DATA_DIR/debug.log $LOG_DIR/scantxoutset-{COMMIT}-$(date +%s).log" \
      "./build/bin/bitcoin-cli -datadir=$DATA_DIR -rpcclienttimeout=0 -named scantxoutset action=start scanobjects=[] >/dev/null"
    
    99003bed87 Merge bitcoin/bitcoin#34536: [29.x] Finalise v29.3
    4d7d5f6b79 Merge bitcoin/bitcoin#34229: [30.2] Backports & Final
    5de2f97a05 dbwrapper: use `SpanReader` for iterator keys
    5c3e0202df dbwrapper: reuse iterator scratch stream
    
    Benchmark 1: ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named scantxoutset action=start scanobjects=[] >/dev/null (COMMIT = 99003bed87333f1be51bf3070235591b3a72f007)
      Time (mean ± σ):     48.556 s ±  0.187 s    [User: 0.002 s, System: 0.000 s]
      Range (min … max):   48.170 s … 48.861 s    10 runs
    
    Benchmark 2: ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named scantxoutset action=start scanobjects=[] >/dev/null (COMMIT = 4d7d5f6b79d4c11c47e7a828d81296918fd11d4d)
      Time (mean ± σ):     43.615 s ±  0.383 s    [User: 0.002 s, System: 0.001 s]
      Range (min … max):   43.245 s … 44.296 s    10 runs
    
    Benchmark 3: ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named scantxoutset action=start scanobjects=[] >/dev/null (COMMIT = 5de2f97a0521fe75b47f9840141c4ace74656de9)
      Time (mean ± σ):     37.307 s ±  0.549 s    [User: 0.003 s, System: 0.001 s]
      Range (min … max):   37.004 s … 38.858 s    10 runs
    
    Benchmark 4: ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named scantxoutset action=start scanobjects=[] >/dev/null (COMMIT = 5c3e0202df9ee53d80fea45e1805992d93a61390)
      Time (mean ± σ):     36.330 s ±  0.264 s    [User: 0.003 s, System: 0.001 s]
      Range (min … max):   36.133 s … 36.916 s    10 runs
    
    Relative speed comparison
            1.34 ±  0.01  ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named scantxoutset action=start scanobjects=[] >/dev/null (COMMIT = 99003bed87333f1be51bf3070235591b3a72f007)
            1.20 ±  0.01  ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named scantxoutset action=start scanobjects=[] >/dev/null (COMMIT = 4d7d5f6b79d4c11c47e7a828d81296918fd11d4d)
            1.03 ±  0.02  ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named scantxoutset action=start scanobjects=[] >/dev/null (COMMIT = 5de2f97a0521fe75b47f9840141c4ace74656de9)
            1.00          ./build/bin/bitcoin-cli -datadir=/mnt/my_storage/BitcoinData -rpcclienttimeout=0 -named scantxoutset action=start scanobjects=[] >/dev/null (COMMIT = 5c3e0202df9ee53d80fea45e1805992d93a61390)
    

    </details>

  18. streams: add `ScopedDataStreamUsage`
    Some DB paths reuse caller-owned `DataStream` scratch buffers across calls.
    
    Add a small RAII helper that asserts the stream is empty on entry and clears it on exit.
    This preserves the old temporary-stream contract while making accidental nested reuse fail fast in debug builds.
    31ce729b28
  19. test: cover repeated dbwrapper stream use
    The next commits reuse scratch streams on `CDBBatch` and `CDBIterator`.
    
    Extend dbwrapper tests to reuse a batch after `Clear()`, seek repeatedly on one iterator, and fail a too-large `CDBIterator::GetValue()` decode before reading the same entry successfully.
    This locks down the reuse and failed-decode contracts before the production changes.
    cb1ab0a716
  20. dbwrapper: guard `CDBBatch` scratch streams
    `CDBBatch` already owns key and value serialization buffers, but manually reserved and cleared them on each call.
    
    Reserve those scratch buffers once in the constructor, then guard `Write()` and `Erase()` use with `ScopedDataStreamUsage`, requiring empty streams on entry and clearing them on every exit path.
    `WriteImpl()` and `EraseImpl()` pass the bytes immediately to LevelDB's write batch, which copies them and does not retain pointers.
    
    `Clear()` now asserts the guards left both scratch streams clean.
    
    Co-authored-by: Andrew Toth <andrewstoth@gmail.com>
    7403c0f907
  21. dbwrapper: reuse iterator scratch stream
    `CDBIterator::Seek()` and `CDBIterator::GetValue()` only need a temporary owning buffer for immediate use.
    
    Keep one preallocated `DataStream` scratch member on the non-copyable iterator and guard each use with `ScopedDataStreamUsage`.
    `SeekImpl()` consumes the serialized key during the immediate LevelDB seek, and `GetValue()` still copies LevelDB value bytes into the owning scratch stream before in-place deobfuscation.
    
    A single stream is enough because these methods cannot overlap on the same iterator without re-entering or concurrently using it, which the guard asserts against.
    The preceding test covers repeated seeks and failed value deserialization followed by a successful read from the same entry.
    
    Co-authored-by: Andrew Toth <andrewstoth@gmail.com>
    032223f403
  22. l0rinc force-pushed on Apr 28, 2026
  23. andrewtoth commented at 2:16 AM on April 29, 2026: contributor

    re-ACK 032223f403d320c3c47c7d5932e333f306efcdc3

  24. optout21 commented at 2:26 PM on April 30, 2026: contributor

    Concept ACK 032223f403d320c3c47c7d5932e333f306efcdc3

  25. jmoik commented at 8:26 PM on May 1, 2026: none

    ACK, on my machine this PR is about 16.6% faster, or 1.20x speedup.

    | Base | 8592152186 | 42.673 s ± 0.369 s | | PR | 032223f403 | 35.757 s ± 0.400 s |


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-05-03 06:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me