Skip to content

Commit dc0c79a

Browse files
committed
Allocate P2WSH/P2TR/P2PK scripts on stack as well
The current `prevector` size of 28 bytes (likely chosen to make `sizeof(CScript)` equal to 32 bytes) was introduced in 2015 (#6914). However, the increasingly common `P2WSH` and `P2TR` scriptPubKeys are 36 bytes, and are forced to use heap (re)allocation rather than efficient inline storage. Increasing the `prevector` size to 36 bytes allows these scripts to be stored on the stack, avoiding heap allocations, reducing potential memory fragmentation, and potentially improving performance during cache flushes (as suggested by the massif analysis showing lower LevelDB arena usage post-flush). Massif analysis confirms a lower stable memory usage after flushing, suggesting the elimination of heap allocations outweighs the larger base size for common workloads. The core trade-off of this change is to eliminate heap allocations for common 36-byte scripts at the cost of increasing the base memory footprint of all CScript objects by 8 bytes. Performance benchmarks for AssumeUTXO load and flush shows: - Small dbcache (450MB): ~1% performance penalty due to more frequent flushes - Large dbcache (4500-4500MB+): ~6-7% performance improvement Full IBD and reindex-chainstate for bigger `dbcache` values also show an overall ~3% speedup.
1 parent 847a891 commit dc0c79a

File tree

5 files changed

+38
-40
lines changed

5 files changed

+38
-40
lines changed

src/bench/checkqueue.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
static const size_t BATCHES = 101;
1818
static const size_t BATCH_SIZE = 30;
19-
static const int PREVECTOR_SIZE = 28;
19+
static const int PREVECTOR_SIZE = 36;
2020
static const unsigned int QUEUE_BATCH_SIZE = 128;
2121

2222
// This Benchmark tests the CheckQueue with a slightly realistic workload,

src/bench/prevector.cpp

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -27,35 +27,35 @@ template <typename T>
2727
static void PrevectorDestructor(benchmark::Bench& bench)
2828
{
2929
bench.batch(2).run([&] {
30-
prevector<28, T> t0;
31-
prevector<28, T> t1;
32-
t0.resize(28);
33-
t1.resize(29);
30+
prevector<36, T> t0;
31+
prevector<36, T> t1;
32+
t0.resize(36);
33+
t1.resize(37);
3434
});
3535
}
3636

3737
template <typename T>
3838
static void PrevectorClear(benchmark::Bench& bench)
3939
{
40-
prevector<28, T> t0;
41-
prevector<28, T> t1;
40+
prevector<36, T> t0;
41+
prevector<36, T> t1;
4242
bench.batch(2).run([&] {
43-
t0.resize(28);
43+
t0.resize(36);
4444
t0.clear();
45-
t1.resize(29);
45+
t1.resize(37);
4646
t1.clear();
4747
});
4848
}
4949

5050
template <typename T>
5151
static void PrevectorResize(benchmark::Bench& bench)
5252
{
53-
prevector<28, T> t0;
54-
prevector<28, T> t1;
53+
prevector<36, T> t0;
54+
prevector<36, T> t1;
5555
bench.batch(4).run([&] {
56-
t0.resize(28);
56+
t0.resize(36);
5757
t0.resize(0);
58-
t1.resize(29);
58+
t1.resize(37);
5959
t1.resize(0);
6060
});
6161
}
@@ -64,8 +64,8 @@ template <typename T>
6464
static void PrevectorDeserialize(benchmark::Bench& bench)
6565
{
6666
DataStream s0{};
67-
prevector<28, T> t0;
68-
t0.resize(28);
67+
prevector<36, T> t0;
68+
t0.resize(36);
6969
for (auto x = 0; x < 900; ++x) {
7070
s0 << t0;
7171
}
@@ -74,7 +74,7 @@ static void PrevectorDeserialize(benchmark::Bench& bench)
7474
s0 << t0;
7575
}
7676
bench.batch(1000).run([&] {
77-
prevector<28, T> t1;
77+
prevector<36, T> t1;
7878
for (auto x = 0; x < 1000; ++x) {
7979
s0 >> t1;
8080
}
@@ -86,7 +86,7 @@ template <typename T>
8686
static void PrevectorFillVectorDirect(benchmark::Bench& bench)
8787
{
8888
bench.run([&] {
89-
std::vector<prevector<28, T>> vec;
89+
std::vector<prevector<36, T>> vec;
9090
vec.reserve(260);
9191
for (size_t i = 0; i < 260; ++i) {
9292
vec.emplace_back();
@@ -99,11 +99,11 @@ template <typename T>
9999
static void PrevectorFillVectorIndirect(benchmark::Bench& bench)
100100
{
101101
bench.run([&] {
102-
std::vector<prevector<28, T>> vec;
102+
std::vector<prevector<36, T>> vec;
103103
vec.reserve(260);
104104
for (size_t i = 0; i < 260; ++i) {
105105
// force allocation
106-
vec.emplace_back(29, T{});
106+
vec.emplace_back(37, T{});
107107
}
108108
});
109109
}

src/script/script.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ class CScriptNum
406406
* Tests in October 2015 showed use of this reduced dbcache memory usage by 23%
407407
* and made an initial sync 13% faster.
408408
*/
409-
typedef prevector<28, unsigned char> CScriptBase;
409+
typedef prevector<36, unsigned char> CScriptBase;
410410

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

src/test/script_tests.cpp

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,7 +1131,7 @@ BOOST_AUTO_TEST_CASE(script_CHECKMULTISIG23)
11311131

11321132
BOOST_AUTO_TEST_CASE(script_size_and_capacity_test)
11331133
{
1134-
BOOST_CHECK_EQUAL(sizeof(CScript), 32);
1134+
BOOST_CHECK_EQUAL(sizeof(CScript), 40);
11351135

11361136
CKey dummyKey;
11371137
dummyKey.MakeNewKey(true);
@@ -1143,7 +1143,7 @@ BOOST_AUTO_TEST_CASE(script_size_and_capacity_test)
11431143
const auto scriptSmallOpReturn{CScript() << OP_RETURN << std::vector<uint8_t>(10, 0xaa)};
11441144
BOOST_CHECK_EQUAL(Solver(scriptSmallOpReturn, dummyVSolutions), TxoutType::NULL_DATA);
11451145
BOOST_CHECK_EQUAL(scriptSmallOpReturn.size(), 12);
1146-
BOOST_CHECK_EQUAL(scriptSmallOpReturn.capacity(), 28);
1146+
BOOST_CHECK_EQUAL(scriptSmallOpReturn.capacity(), 36);
11471147
BOOST_CHECK_EQUAL(scriptSmallOpReturn.allocated_memory(), 0);
11481148
}
11491149

@@ -1152,7 +1152,7 @@ BOOST_AUTO_TEST_CASE(script_size_and_capacity_test)
11521152
const auto scriptP2WPKH{GetScriptForDestination(WitnessV0KeyHash{PKHash{CKeyID{CPubKey{dummyKey.GetPubKey()}.GetID()}}})};
11531153
BOOST_CHECK_EQUAL(Solver(scriptP2WPKH, dummyVSolutions), TxoutType::WITNESS_V0_KEYHASH);
11541154
BOOST_CHECK_EQUAL(scriptP2WPKH.size(), 22);
1155-
BOOST_CHECK_EQUAL(scriptP2WPKH.capacity(), 28);
1155+
BOOST_CHECK_EQUAL(scriptP2WPKH.capacity(), 36);
11561156
BOOST_CHECK_EQUAL(scriptP2WPKH.allocated_memory(), 0);
11571157
}
11581158

@@ -1161,7 +1161,7 @@ BOOST_AUTO_TEST_CASE(script_size_and_capacity_test)
11611161
const auto scriptP2SH{GetScriptForDestination(ScriptHash{CScript{CScript{} << OP_TRUE}})};
11621162
BOOST_CHECK(scriptP2SH.IsPayToScriptHash());
11631163
BOOST_CHECK_EQUAL(scriptP2SH.size(), 23);
1164-
BOOST_CHECK_EQUAL(scriptP2SH.capacity(), 28);
1164+
BOOST_CHECK_EQUAL(scriptP2SH.capacity(), 36);
11651165
BOOST_CHECK_EQUAL(scriptP2SH.allocated_memory(), 0);
11661166
}
11671167

@@ -1170,35 +1170,35 @@ BOOST_AUTO_TEST_CASE(script_size_and_capacity_test)
11701170
const auto scriptP2PKH{GetScriptForDestination(PKHash{CKeyID{CPubKey{dummyKey.GetPubKey()}.GetID()}})};
11711171
BOOST_CHECK_EQUAL(Solver(scriptP2PKH, dummyVSolutions), TxoutType::PUBKEYHASH);
11721172
BOOST_CHECK_EQUAL(scriptP2PKH.size(), 25);
1173-
BOOST_CHECK_EQUAL(scriptP2PKH.capacity(), 28);
1173+
BOOST_CHECK_EQUAL(scriptP2PKH.capacity(), 36);
11741174
BOOST_CHECK_EQUAL(scriptP2PKH.allocated_memory(), 0);
11751175
}
11761176

1177-
// P2WSH is heap allocated
1177+
// P2WSH is stack allocated
11781178
{
11791179
const auto scriptP2WSH{GetScriptForDestination(WitnessV0ScriptHash{CScript{CScript{} << OP_TRUE}})};
11801180
BOOST_CHECK(scriptP2WSH.IsPayToWitnessScriptHash());
11811181
BOOST_CHECK_EQUAL(scriptP2WSH.size(), 34);
1182-
BOOST_CHECK_EQUAL(scriptP2WSH.capacity(), 34);
1183-
BOOST_CHECK_EQUAL(scriptP2WSH.allocated_memory(), 34);
1182+
BOOST_CHECK_EQUAL(scriptP2WSH.capacity(), 36);
1183+
BOOST_CHECK_EQUAL(scriptP2WSH.allocated_memory(), 0);
11841184
}
11851185

1186-
// P2TR is heap allocated
1186+
// P2TR is stack allocated
11871187
{
11881188
const auto scriptTaproot{GetScriptForDestination(WitnessV1Taproot{XOnlyPubKey{CPubKey{dummyKey.GetPubKey()}}})};
11891189
BOOST_CHECK_EQUAL(Solver(scriptTaproot, dummyVSolutions), TxoutType::WITNESS_V1_TAPROOT);
11901190
BOOST_CHECK_EQUAL(scriptTaproot.size(), 34);
1191-
BOOST_CHECK_EQUAL(scriptTaproot.capacity(), 34);
1192-
BOOST_CHECK_EQUAL(scriptTaproot.allocated_memory(), 34);
1191+
BOOST_CHECK_EQUAL(scriptTaproot.capacity(), 36);
1192+
BOOST_CHECK_EQUAL(scriptTaproot.allocated_memory(), 0);
11931193
}
11941194

1195-
// P2PK is heap allocated
1195+
// P2PK is stack allocated
11961196
{
11971197
const auto scriptPubKey{GetScriptForRawPubKey(CPubKey{dummyKey.GetPubKey()})};
11981198
BOOST_CHECK_EQUAL(Solver(scriptPubKey, dummyVSolutions), TxoutType::PUBKEY);
11991199
BOOST_CHECK_EQUAL(scriptPubKey.size(), 35);
1200-
BOOST_CHECK_EQUAL(scriptPubKey.capacity(), 35);
1201-
BOOST_CHECK_EQUAL(scriptPubKey.allocated_memory(), 35);
1200+
BOOST_CHECK_EQUAL(scriptPubKey.capacity(), 36);
1201+
BOOST_CHECK_EQUAL(scriptPubKey.allocated_memory(), 0);
12021202
}
12031203

12041204
// Small MULTISIG is heap allocated
@@ -1207,8 +1207,8 @@ BOOST_AUTO_TEST_CASE(script_size_and_capacity_test)
12071207
const auto scriptMultisig{GetScriptForMultisig(1, keys)};
12081208
BOOST_CHECK_EQUAL(Solver(scriptMultisig, dummyVSolutions), TxoutType::MULTISIG);
12091209
BOOST_CHECK_EQUAL(scriptMultisig.size(), 37);
1210-
BOOST_CHECK_EQUAL(scriptMultisig.capacity(), 52);
1211-
BOOST_CHECK_EQUAL(scriptMultisig.allocated_memory(), 52);
1210+
BOOST_CHECK_EQUAL(scriptMultisig.capacity(), 55);
1211+
BOOST_CHECK_EQUAL(scriptMultisig.allocated_memory(), 55);
12121212
}
12131213
}
12141214

src/test/validation_flush_tests.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +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-
//
32-
// See also: Coin::DynamicMemoryUsage().
29+
// The number of bytes consumed by coin's heap data, i.e. CScript (prevector<36, unsigned char>)
30+
// when assigned 56 bytes of data per above. See also: Coin::DynamicMemoryUsage().
3331
constexpr unsigned int COIN_SIZE = is_64_bit ? 80 : 64;
3432

3533
auto print_view_mem_usage = [](CCoinsViewCache& view) {

0 commit comments

Comments
 (0)