Line data Source code
1 : #define _GNU_SOURCE
2 :
3 : #include "fd_accdb.h"
4 : #include "../../util/fd_util.h"
5 :
6 : #include <stdlib.h>
7 : #include <string.h>
8 : #include <unistd.h>
9 : #include <sys/mman.h>
10 :
11 : /* bench_accdb_txn: Simulate realistic mainnet transaction patterns
12 : against the accounts database in a tight loop.
13 :
14 : Each simulated transaction acquires a mix of read-only and read-write
15 : accounts, optionally mutates the writable ones, then releases with
16 : commit or revert based on measured mainnet failure rates.
17 :
18 : The transaction mix, account counts, data sizes, and commit/revert
19 : ratios are derived from a 1000-slot mainnet replay
20 : (slots 406545575-406546575, ~1.24M transactions).
21 :
22 : The benchmark pre-populates a pool of accounts with realistic sizes,
23 : then runs the acquire/release loop for a configurable duration,
24 : reporting aggregate throughput.
25 :
26 : This is NOT a full transaction execution benchmark — it purely
27 : measures the accounts database hot path (cache hits, acquire/release,
28 : commit/revert) under a realistic workload shape. */
29 :
30 : static uchar dummy_owner[ 32 ] = { 0xEE };
31 :
32 : /* Maximum data buffer for writes. Writable accounts need staging
33 : space; we cap at a reasonable size for the benchmark. */
34 : #define BENCH_MAX_DATA_SZ (10UL<<20UL) /* 10 MiB max */
35 : #define BENCH_MAX_ACCTS_PER_TXN (64UL)
36 0 : #define BENCH_CACHE_FOOTPRINT (16UL<<30UL)
37 :
38 : /* Transaction archetype: a representative mix of account access
39 : patterns observed on mainnet. Each archetype specifies:
40 : - ro_cnt / rw_cnt: number of read-only / read-write accounts
41 : - ro_data_sz / rw_data_sz: representative per-account data size
42 : - weight: frequency in parts per 10000
43 : - fail_rate: failure probability in parts per 10000
44 :
45 : Derived from the per-transaction histograms measured on
46 : mainnet-406545575-perf (1000 slots, 1.24M txns). The archetypes
47 : cover ~99% of observed transactions. */
48 :
49 : struct txn_archetype {
50 : uint ro_cnt;
51 : uint rw_cnt;
52 : uint ro_data_sz; /* representative per-account read-only data size */
53 : uint rw_data_sz; /* representative per-account read-write data size */
54 : uint weight; /* parts per 10000 */
55 : uint fail_ppm; /* failure rate in parts per 10000 */
56 : };
57 :
58 : /* Archetypes derived from the histogram data:
59 :
60 : ~62% of txns: 1 RO, 2 RW — simple transfers/token ops
61 : - RO: mostly <128 B (program accounts)
62 : - RW: mostly 165-500 B (token accounts)
63 : - Fail rate: ~0.25% of these (very low)
64 :
65 : ~3% of txns: 2 RO, 2-3 RW — token swaps (simple)
66 : - RO: 128-512 B
67 : - RW: 165-512 B
68 : - Fail rate: ~4%
69 :
70 : ~4% of txns: 4 RO, 4 RW — small DeFi interactions
71 : - RO: 128-1K
72 : - RW: 256-1K
73 : - Fail rate: ~6%
74 :
75 : ~5% of txns: 5-8 RO, 5-8 RW — medium DeFi/AMM swaps
76 : - RO: 1K-4K per account
77 : - RW: 512-2K per account
78 : - Fail rate: ~7%
79 :
80 : ~14% of txns: 12 RO, 12 RW — complex DeFi (Raydium, Orca, etc.)
81 : - RO: 8K per account (64K-128K total / ~12 accounts)
82 : - RW: 2K per account (16K-32K total / ~12 accounts)
83 : - Fail rate: ~27%
84 :
85 : ~9% of txns: 24 RO, 24 RW — very complex DeFi, multi-hop routes
86 : - RO: 8K per account (128K-256K total / ~24 accounts)
87 : - RW: 2K per account (32K-64K total / ~24 accounts)
88 : - Fail rate: ~34%
89 :
90 : ~4% of txns: 2 RO, 48 RW — bulk operations (token-2022, etc.)
91 : - RO: 128 B
92 : - RW: 165 B
93 : - Fail rate: ~17%
94 : */
95 :
96 : static struct txn_archetype const TXN_ARCHETYPES[] = {
97 : /* ro rw ro_sz rw_sz weight fail */
98 : { 0, 1, 0, 165, 300, 25 }, /* single write (baseline) */
99 : { 1, 2, 82, 165, 5930, 25 }, /* simple transfer/token op */
100 : { 2, 3, 200, 300, 330, 400 }, /* simple swap */
101 : { 4, 4, 512, 512, 370, 600 }, /* small DeFi */
102 : { 6, 6, 2048, 1024, 505, 700 }, /* medium DeFi / AMM */
103 : { 12, 12, 8192, 2048, 1354, 2700 }, /* complex DeFi (12+12) */
104 : { 24, 24, 8192, 2048, 891, 3400 }, /* multi-hop DeFi (24+24) */
105 : { 2, 48, 128, 165, 320, 1700 }, /* bulk operations */
106 : };
107 :
108 : #define TXN_ARCHETYPE_CNT (sizeof(TXN_ARCHETYPES)/sizeof(TXN_ARCHETYPES[0]))
109 :
110 : static void
111 : make_pubkey( uchar pubkey[ static 32 ],
112 0 : ulong idx ) {
113 0 : fd_memset( pubkey, 0, 32UL );
114 0 : fd_memcpy( pubkey, &idx, sizeof(ulong) );
115 0 : }
116 :
117 : static fd_accdb_t *
118 : bench_setup( int * out_fd,
119 : ulong max_accounts,
120 : ulong max_live_slots,
121 : ulong max_account_writes_per_slot,
122 : ulong partition_cnt,
123 0 : ulong partition_sz ) {
124 0 : int fd = memfd_create( "accdb_txn", 0 );
125 0 : if( FD_UNLIKELY( fd<0 ) ) FD_LOG_ERR(( "memfd_create failed" ));
126 0 : *out_fd = fd;
127 :
128 0 : ulong cache_fp = BENCH_CACHE_FOOTPRINT;
129 0 : ulong shmem_fp = fd_accdb_shmem_footprint( max_accounts, max_live_slots,
130 0 : max_account_writes_per_slot,
131 0 : partition_cnt, cache_fp, 640UL, 1UL );
132 0 : FD_TEST( shmem_fp );
133 0 : void * shmem_mem = aligned_alloc( fd_accdb_shmem_align(), shmem_fp );
134 0 : FD_TEST( shmem_mem );
135 0 : fd_accdb_shmem_t * shmem = fd_accdb_shmem_join(
136 0 : fd_accdb_shmem_new( shmem_mem, max_accounts, max_live_slots,
137 0 : max_account_writes_per_slot, partition_cnt,
138 0 : partition_sz, cache_fp, 640UL, 0, 42UL, 1UL ) );
139 0 : FD_TEST( shmem );
140 :
141 0 : ulong accdb_fp = fd_accdb_footprint( max_live_slots );
142 0 : FD_TEST( accdb_fp );
143 0 : void * accdb_mem = aligned_alloc( fd_accdb_align(), accdb_fp );
144 0 : FD_TEST( accdb_mem );
145 0 : fd_accdb_t * accdb = fd_accdb_join( fd_accdb_new( accdb_mem, shmem, fd, 0UL, NULL ) );
146 0 : FD_TEST( accdb );
147 0 : return accdb;
148 0 : }
149 :
150 : int
151 : main( int argc,
152 : char ** argv ) {
153 : fd_boot( &argc, &argv );
154 :
155 : ulong account_cnt = fd_env_strip_cmdline_ulong( &argc, &argv, "--accounts", NULL, 50000UL );
156 : ulong duration_ns = fd_env_strip_cmdline_ulong( &argc, &argv, "--duration", NULL, 15000000000UL );
157 : uint seed = fd_env_strip_cmdline_uint ( &argc, &argv, "--seed", NULL, 42U );
158 :
159 : FD_LOG_NOTICE(( "accdb txn-pattern bench"
160 : " (accounts=%lu duration=%.1f s seed=%u)",
161 : account_cnt, (double)duration_ns/1e9, seed ));
162 :
163 : fd_rng_t _rng[1];
164 : fd_rng_t * rng = fd_rng_join( fd_rng_new( _rng, seed, 0UL ) );
165 : FD_TEST( rng );
166 :
167 : /* Setup */
168 : int fd;
169 : ulong partition_sz = 1UL<<30UL;
170 : ulong partition_cnt = 8192UL;
171 : fd_accdb_t * accdb = bench_setup( &fd,
172 : 1200000000UL,
173 : 4096UL,
174 : (uint)account_cnt + 4096U,
175 : partition_cnt,
176 : partition_sz );
177 :
178 : fd_accdb_fork_id_t root = fd_accdb_attach_child(
179 : accdb, (fd_accdb_fork_id_t){ .val = USHORT_MAX } );
180 : fd_accdb_fork_id_t fork = fd_accdb_attach_child( accdb, root );
181 :
182 : /* Pre-populate accounts with a distribution of sizes matching
183 : mainnet. Use a weighted mix: most accounts are 165 B (token
184 : accounts), with some larger ones for DeFi state. */
185 : FD_LOG_NOTICE(( "populating %lu accounts ...", account_cnt ));
186 : {
187 : /* Size distribution for populating: weight/1000, avg_sz */
188 : static const struct { uint weight; uint sz; } pop_dist[] = {
189 : { 650, 165 }, /* token accounts */
190 : { 200, 82 }, /* small / program-derived */
191 : { 50, 512 }, /* medium state */
192 : { 40, 2048 }, /* AMM pool state */
193 : { 40, 8192 }, /* large DeFi state (e.g. Raydium) */
194 : { 20, 165 }, /* misc */
195 : };
196 : ulong pop_dist_cnt = sizeof(pop_dist)/sizeof(pop_dist[0]);
197 :
198 : uchar pubkey[ 32 ];
199 : for( ulong i=0UL; i<account_cnt; i++ ) {
200 : make_pubkey( pubkey, i );
201 :
202 : /* Sample size from population distribution */
203 : uint r = fd_rng_uint( rng ) % 1000U;
204 : uint cumul = 0U;
205 : ulong sz = 165UL;
206 : for( ulong d=0UL; d<pop_dist_cnt; d++ ) {
207 : cumul += pop_dist[ d ].weight;
208 : if( r<cumul ) { sz = (ulong)pop_dist[ d ].sz; break; }
209 : }
210 :
211 : uchar const * pks[1] = { pubkey };
212 : int wr[1] = { 1 };
213 : fd_acc_t acc[1];
214 : memset( acc, 0, sizeof(acc) );
215 : fd_accdb_acquire( accdb, fork, 1UL, pks, wr, acc );
216 : acc[0].lamports = i + 1UL;
217 : acc[0].data_len = sz;
218 : memcpy( acc[0].owner, dummy_owner, 32UL );
219 : memset( acc[0].data, (uchar)(i & 0xFFUL), sz );
220 : acc[0].commit = 1;
221 : fd_accdb_release( accdb, 1UL, acc );
222 : }
223 : }
224 :
225 : /* Warm: touch every account once to ensure cache residency */
226 : {
227 : uchar pubkey[ 32 ];
228 : for( ulong i=0UL; i<account_cnt; i++ ) {
229 : make_pubkey( pubkey, i );
230 : uchar const * pks[1] = { pubkey };
231 : int wr[1] = { 0 };
232 : fd_acc_t acc[1];
233 : memset( acc, 0, sizeof(acc) );
234 : fd_accdb_acquire( accdb, fork, 1UL, pks, wr, acc );
235 : fd_accdb_release( accdb, 1UL, acc );
236 : }
237 : }
238 :
239 : FD_LOG_NOTICE(( "populated, starting txn-pattern loop" ));
240 :
241 : /* Baseline: single-account read-only acquire/release for
242 : comparison with the multi-account txn patterns below. */
243 : {
244 : ulong baseline_ops = 0UL;
245 : long bl_start = fd_log_wallclock();
246 : long bl_stop = bl_start + (long)(duration_ns / 5UL); /* 1/5 of total */
247 : uchar pk[ 32 ];
248 : while( fd_log_wallclock()<bl_stop ) {
249 : for( ulong b=0UL; b<1000UL; b++ ) {
250 : ulong idx = fd_rng_ulong( rng ) % account_cnt;
251 : make_pubkey( pk, idx );
252 : uchar const * pks[1] = { pk };
253 : int wr[1] = { 0 };
254 : fd_acc_t acc[1];
255 : memset( acc, 0, sizeof(acc) );
256 : fd_accdb_acquire( accdb, fork, 1UL, pks, wr, acc );
257 : fd_accdb_release( accdb, 1UL, acc );
258 : baseline_ops++;
259 : }
260 : }
261 : long bl_elapsed = fd_log_wallclock() - bl_start;
262 : FD_LOG_NOTICE(( " baseline single-RO: %lu ops in %.3f s"
263 : " (%.0f ops/s, %.0f ns/acc)",
264 : baseline_ops,
265 : (double)bl_elapsed / 1e9,
266 : (double)baseline_ops / ( (double)bl_elapsed / 1e9 ),
267 : (double)bl_elapsed / (double)baseline_ops ));
268 : }
269 :
270 : /* Compute cumulative weights for archetype selection */
271 : uint archetype_cumul[ TXN_ARCHETYPE_CNT ];
272 : {
273 : uint sum = 0U;
274 : for( ulong i=0UL; i<TXN_ARCHETYPE_CNT; i++ ) {
275 : sum += TXN_ARCHETYPES[ i ].weight;
276 : archetype_cumul[ i ] = sum;
277 : }
278 : /* Normalize: if weights don't sum to 10000 exactly, the last
279 : bucket catches the remainder. */
280 : }
281 :
282 : /* Pre-allocate per-txn arrays on the stack. */
283 : uchar pubkeys_buf[ BENCH_MAX_ACCTS_PER_TXN ][ 32 ];
284 : uchar const * pubkey_ptrs[ BENCH_MAX_ACCTS_PER_TXN ];
285 : int writable[ BENCH_MAX_ACCTS_PER_TXN ];
286 : fd_acc_t accs[ BENCH_MAX_ACCTS_PER_TXN ];
287 :
288 : /* Per-archetype counters for reporting */
289 : ulong arch_txn_cnt[ TXN_ARCHETYPE_CNT ];
290 : ulong arch_commit_cnt[ TXN_ARCHETYPE_CNT ];
291 : long arch_ns[ TXN_ARCHETYPE_CNT ];
292 : memset( arch_txn_cnt, 0, sizeof(arch_txn_cnt) );
293 : memset( arch_commit_cnt, 0, sizeof(arch_commit_cnt) );
294 : memset( arch_ns, 0, sizeof(arch_ns) );
295 :
296 : ulong txn_cnt = 0UL;
297 : long start = fd_log_wallclock();
298 : long stop = start + (long)duration_ns;
299 :
300 : while( fd_log_wallclock()<stop ) {
301 : /* Batch 100 txns between clock checks to amortize syscall */
302 : for( ulong b=0UL; b<100UL; b++ ) {
303 :
304 : /* 1. Pick a transaction archetype */
305 : uint r = fd_rng_uint( rng ) % 10000U;
306 : ulong arch_idx = 0UL;
307 : for( ulong i=0UL; i<TXN_ARCHETYPE_CNT; i++ ) {
308 : if( r<archetype_cumul[ i ] ) { arch_idx = i; break; }
309 : }
310 : struct txn_archetype const * arch = &TXN_ARCHETYPES[ arch_idx ];
311 :
312 : ulong total_cnt = (ulong)arch->ro_cnt + (ulong)arch->rw_cnt;
313 :
314 : /* 2. Pick unique random accounts for this txn.
315 : RW accounts come first, then RO accounts. */
316 : for( ulong i=0UL; i<total_cnt; i++ ) {
317 : ulong idx = fd_rng_ulong( rng ) % account_cnt;
318 : make_pubkey( pubkeys_buf[ i ], idx );
319 : pubkey_ptrs[ i ] = pubkeys_buf[ i ];
320 : writable[ i ] = ( i < (ulong)arch->rw_cnt ) ? 1 : 0;
321 : }
322 : memset( accs, 0, total_cnt * sizeof(fd_acc_t) );
323 :
324 : /* 3. Acquire */
325 : long t0 = fd_log_wallclock();
326 : fd_accdb_acquire( accdb, fork, total_cnt,
327 : pubkey_ptrs, writable, accs );
328 :
329 : /* 4. Decide commit or revert */
330 : int do_commit = ( (fd_rng_uint( rng ) % 10000U) >= arch->fail_ppm );
331 :
332 : /* 5. For writable accs, set commit flag and touch data */
333 : for( ulong i=0UL; i<(ulong)arch->rw_cnt; i++ ) {
334 : accs[ i ].commit = do_commit;
335 : if( do_commit && accs[ i ].data ) {
336 : /* Touch a byte to simulate mutation */
337 : accs[ i ].data[ 0 ] ^= 0x01;
338 : }
339 : }
340 :
341 : /* 6. Release */
342 : fd_accdb_release( accdb, total_cnt, accs );
343 : long t1 = fd_log_wallclock();
344 :
345 : arch_txn_cnt[ arch_idx ]++;
346 : arch_commit_cnt[ arch_idx ] += (ulong)do_commit;
347 : arch_ns[ arch_idx ] += ( t1 - t0 );
348 : txn_cnt++;
349 : }
350 : }
351 :
352 : long elapsed = fd_log_wallclock() - start;
353 : double secs = (double)elapsed / 1e9;
354 :
355 : /* Report per-archetype results */
356 : FD_LOG_NOTICE(( "--- bench_accdb_txn results"
357 : " (%lu txns in %.3f s, %.0f txn/s) ---",
358 : txn_cnt, secs, (double)txn_cnt / secs ));
359 : FD_LOG_NOTICE(( " %-6s %4s %4s %7s %10s %8s %10s %10s %10s",
360 : "Arch", "RO", "RW", "Wt%", "Txns", "Commit%",
361 : "Txn/s", "ns/txn", "ns/acc" ));
362 :
363 : ulong total_commit = 0UL;
364 : ulong total_accts = 0UL;
365 : for( ulong i=0UL; i<TXN_ARCHETYPE_CNT; i++ ) {
366 : if( !arch_txn_cnt[ i ] ) continue;
367 : total_commit += arch_commit_cnt[ i ];
368 : ulong accts_per_txn = (ulong)TXN_ARCHETYPES[ i ].ro_cnt
369 : + (ulong)TXN_ARCHETYPES[ i ].rw_cnt;
370 : ulong arch_total_accts = arch_txn_cnt[ i ] * accts_per_txn;
371 : total_accts += arch_total_accts;
372 : double a_secs = (double)arch_ns[ i ] / 1e9;
373 : FD_LOG_NOTICE(( " %-6lu %4u %4u %6.1f%% %10lu %7.2f%% %10.0f %10.0f %10.0f",
374 : i,
375 : TXN_ARCHETYPES[ i ].ro_cnt,
376 : TXN_ARCHETYPES[ i ].rw_cnt,
377 : (double)TXN_ARCHETYPES[ i ].weight / 100.0,
378 : arch_txn_cnt[ i ],
379 : 100.0 * (double)arch_commit_cnt[ i ]
380 : / (double)arch_txn_cnt[ i ],
381 : (double)arch_txn_cnt[ i ] / a_secs,
382 : (double)arch_ns[ i ]
383 : / (double)arch_txn_cnt[ i ],
384 : (double)arch_ns[ i ]
385 : / (double)arch_total_accts ));
386 : }
387 :
388 : FD_LOG_NOTICE(( " TOTAL: %lu txns, %.2f%% committed, "
389 : "%.0f txn/s, %.0f ns/txn, %.0f ns/acc",
390 : txn_cnt,
391 : 100.0 * (double)total_commit / (double)txn_cnt,
392 : (double)txn_cnt / secs,
393 : (double)elapsed / (double)txn_cnt,
394 : (double)elapsed / (double)total_accts ));
395 :
396 : /* Compute mainnet-weighted average ns/acc from per-archetype
397 : measurements and the archetype weights. */
398 : {
399 : double weighted_ns_per_acc = 0.0;
400 : double weight_sum = 0.0;
401 : for( ulong i=0UL; i<TXN_ARCHETYPE_CNT; i++ ) {
402 : if( !arch_txn_cnt[ i ] ) continue;
403 : ulong accts_per_txn = (ulong)TXN_ARCHETYPES[ i ].ro_cnt
404 : + (ulong)TXN_ARCHETYPES[ i ].rw_cnt;
405 : double ns_per_acc = (double)arch_ns[ i ]
406 : / (double)( arch_txn_cnt[ i ] * accts_per_txn );
407 : double w = (double)TXN_ARCHETYPES[ i ].weight;
408 : double accts_w = w * (double)accts_per_txn;
409 : weighted_ns_per_acc += ns_per_acc * accts_w;
410 : weight_sum += accts_w;
411 : }
412 : if( weight_sum>0.0 ) {
413 : FD_LOG_NOTICE(( " Mainnet-weighted avg: %.0f ns/acc"
414 : " (weighted by archetype frequency"
415 : " x accounts per txn)",
416 : weighted_ns_per_acc / weight_sum ));
417 : }
418 : }
419 :
420 : fd_rng_delete( fd_rng_leave( rng ) );
421 : close( fd );
422 :
423 : FD_LOG_NOTICE(( "pass" ));
424 : fd_halt();
425 : return 0;
426 : }
|