Skip to content

Commit d5104cf

Browse files
l0rincachow101andrewtothmaflcko
committed
prevector: store P2WSH/P2TR/P2PK scripts inline
The current `prevector` size of 28 bytes (chosen to fill the `sizeof(CScript)` aligned size) was introduced in 2015 (bitcoin/bitcoin#6914) before SegWit and TapRoot. However, the increasingly common `P2WSH` and `P2TR` scripts are both 34 bytes, and are forced to use heap (re)allocation rather than efficient inline storage. The core trade-off of this change is to eliminate heap allocations for common 34-36 byte scripts at the cost of increasing the base memory footprint of all `CScript` objects by 8 bytes (while still respecting peak memory usage defined by `-dbcache`). Increasing the `prevector` size allows these scripts to be stored inline, avoiding extra heap allocations, reducing potential memory fragmentation, and improving performance during cache flushes. Massif analysis confirms a lower stable memory usage after flushing, suggesting the elimination of heap allocations outweighs the larger base size for common workloads. Due to memory alignment, increasing the `prevector` size to 36 bytes doesn't change the overall `sizeof(CScript)` compared to an increase to 34 bytes, allowing us to include `P2PK` scripts as well at no additional memory cost. Performance benchmarks for AssumeUTXO load and flush show: * Small dbcache (450MB): ~1-3% performance improvement (despite more frequent flushes) * Large dbcache (4500MB): ~6-8% performance improvement due to fewer heap allocations (and basically the number of flushes) * Very large dbcache (4500MB): ~5-6% performance improvement due to fewer heap allocations (and memory limit not being reached, so there's no memory penalty) Full IBD and reindex-chainstate with larger `dbcache` values also show an overall ~3-4% speedup. Co-authored-by: Ava Chow <[email protected]> Co-authored-by: Andrew Toth <[email protected]> Co-authored-by: maflcko <[email protected]>
1 parent 5212150 commit d5104cf

File tree

3 files changed

+12
-15
lines changed

3 files changed

+12
-15
lines changed

src/script/script.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,10 +403,8 @@ class CScriptNum
403403
/**
404404
* We use a prevector for the script to reduce the considerable memory overhead
405405
* of vectors in cases where they normally contain a small number of small elements.
406-
* Tests in October 2015 showed use of this reduced dbcache memory usage by 23%
407-
* and made an initial sync 13% faster.
408406
*/
409-
using CScriptBase = prevector<28, uint8_t>;
407+
using CScriptBase = prevector<36, uint8_t>;
410408

411409
bool GetScriptOp(CScriptBase::const_iterator& pc, CScriptBase::const_iterator end, opcodetype& opcodeRet, std::vector<unsigned char>* pvchRet);
412410

src/test/script_tests.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,10 +1175,10 @@ static TxoutType GetTxoutType(const CScript& output_script)
11751175
BOOST_AUTO_TEST_CASE(script_size_and_capacity_test)
11761176
{
11771177
BOOST_CHECK_EQUAL(sizeof(CompressedScript), 40);
1178-
BOOST_CHECK_EQUAL(sizeof(CScriptBase), 32);
1178+
BOOST_CHECK_EQUAL(sizeof(CScriptBase), 40);
11791179
BOOST_CHECK_NE(sizeof(CScriptBase), sizeof(prevector<CScriptBase::STATIC_SIZE + 1, uint8_t>)); // CScriptBase size should be set to avoid wasting space in padding
1180-
BOOST_CHECK_EQUAL(sizeof(CScript), 32);
1181-
BOOST_CHECK_EQUAL(sizeof(CTxOut), 40);
1180+
BOOST_CHECK_EQUAL(sizeof(CScript), 40);
1181+
BOOST_CHECK_EQUAL(sizeof(CTxOut), 48);
11821182

11831183
CKey dummy_key;
11841184
dummy_key.MakeNewKey(/*fCompressed=*/true);
@@ -1212,25 +1212,25 @@ BOOST_AUTO_TEST_CASE(script_size_and_capacity_test)
12121212
CHECK_SCRIPT_STATIC_SIZE(script, 25);
12131213
}
12141214

1215-
// P2WSH needs extra allocation
1215+
// P2WSH has direct allocation
12161216
{
12171217
const auto script{GetScriptForDestination(WitnessV0ScriptHash{CScript{} << OP_TRUE})};
12181218
BOOST_CHECK(script.IsPayToWitnessScriptHash());
1219-
CHECK_SCRIPT_DYNAMIC_SIZE(script, 34, 34);
1219+
CHECK_SCRIPT_STATIC_SIZE(script, 34);
12201220
}
12211221

1222-
// P2TR needs extra allocation
1222+
// P2TR has direct allocation
12231223
{
12241224
const auto script{GetScriptForDestination(WitnessV1Taproot{XOnlyPubKey{dummy_pubkey}})};
12251225
BOOST_CHECK_EQUAL(GetTxoutType(script), TxoutType::WITNESS_V1_TAPROOT);
1226-
CHECK_SCRIPT_DYNAMIC_SIZE(script, 34, 34);
1226+
CHECK_SCRIPT_STATIC_SIZE(script, 34);
12271227
}
12281228

1229-
// Compressed P2PK needs extra allocation
1229+
// Compressed P2PK has direct allocation
12301230
{
12311231
const auto script{GetScriptForRawPubKey(dummy_pubkey)};
12321232
BOOST_CHECK_EQUAL(GetTxoutType(script), TxoutType::PUBKEY);
1233-
CHECK_SCRIPT_DYNAMIC_SIZE(script, 35, 35);
1233+
CHECK_SCRIPT_STATIC_SIZE(script, 35);
12341234
}
12351235

12361236
// Uncompressed P2PK needs extra allocation

src/test/validation_flush_tests.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,8 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate)
2626
LOCK(::cs_main);
2727
auto& view = chainstate.CoinsTip();
2828

29-
// The number of bytes consumed by coin's heap data, i.e. CScript
30-
// (prevector<28, unsigned char>) when assigned 56 bytes of data per above.
31-
//
29+
// The number of bytes consumed by coin's heap data, i.e.
30+
// CScript (prevector<36, unsigned char>) when assigned 56 bytes of data per above.
3231
// See also: Coin::DynamicMemoryUsage().
3332
constexpr unsigned int COIN_SIZE = is_64_bit ? 80 : 64;
3433

0 commit comments

Comments
 (0)