LCOV - code coverage report
Current view: top level - flamenco/accdb - bench_accdb_txn.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 29 0.0 %
Date: 2026-06-30 05:50:37 Functions: 0 2 0.0 %

          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             : }

Generated by: LCOV version 1.14