LCOV - code coverage report
Current view: top level - flamenco/accdb - bench_accdb_cleanup.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 228 0.0 %
Date: 2026-09-17 04:28:31 Functions: 0 14 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             : #include <pthread.h>
      11             : #include <sched.h>
      12             : #include <time.h>
      13             : 
      14             : /* Measures only the T2 command body for root advancement and fork
      15             :    purge.  T1 prepares one zero-data account version per pubkey before
      16             :    submitting the command.  Reusing the same pubkeys each slot forces
      17             :    root cleanup to find superseded versions while keeping account data
      18             :    size out of this metadata-focused benchmark.
      19             : 
      20             :    Pin T1 and T2 to separate physical cores for repeatable results:
      21             : 
      22             :      bench_accdb_cleanup --t1-cpu 94 --t2-cpu 95 */
      23             : 
      24           0 : #define BENCH_CACHE_FOOTPRINT    (64UL<<20UL)
      25           0 : #define BENCH_CACHE_MIN_RESERVED (2UL)
      26           0 : #define BENCH_MAX_LIVE_SLOTS     (8UL)
      27           0 : #define BENCH_PARTITION_CNT      (64UL)
      28           0 : #define BENCH_PARTITION_SZ       (1UL<<30UL)
      29             : 
      30             : typedef struct {
      31             :   fd_accdb_t * t1;
      32             :   fd_accdb_t * t2;
      33             :   void *       t1_mem;
      34             :   void *       t2_mem;
      35             :   void *       shmem_mem;
      36             :   int          fd;
      37             : } bench_env_t;
      38             : 
      39             : static bench_env_t
      40           0 : bench_setup( ulong writes_per_slot ) {
      41           0 :   FD_TEST( writes_per_slot );
      42           0 :   FD_TEST( writes_per_slot<=(UINT_MAX-1024UL)/5UL );
      43           0 :   FD_TEST( writes_per_slot<=UINT_MAX/BENCH_MAX_LIVE_SLOTS-16UL );
      44             : 
      45           0 :   ulong max_accounts = 5UL*writes_per_slot + 1024UL;
      46           0 :   ulong max_writes   = writes_per_slot + 16UL;
      47             : 
      48           0 :   int fd = memfd_create( "accdb_cleanup_bench", 0 );
      49           0 :   if( FD_UNLIKELY( fd<0 ) ) FD_LOG_ERR(( "memfd_create failed" ));
      50             : 
      51           0 :   ulong shmem_fp = fd_accdb_shmem_footprint( max_accounts,
      52           0 :                                               BENCH_MAX_LIVE_SLOTS,
      53           0 :                                               max_writes,
      54           0 :                                               BENCH_PARTITION_CNT,
      55           0 :                                               BENCH_CACHE_FOOTPRINT,
      56           0 :                                               BENCH_CACHE_MIN_RESERVED,
      57           0 :                                               2UL,
      58           0 :                                               0UL );
      59           0 :   FD_TEST( shmem_fp );
      60           0 :   void * shmem_mem = aligned_alloc( fd_accdb_shmem_align(), shmem_fp );
      61           0 :   FD_TEST( shmem_mem );
      62           0 :   fd_accdb_shmem_t * shmem = fd_accdb_shmem_join(
      63           0 :       fd_accdb_shmem_new( shmem_mem,
      64           0 :                           max_accounts,
      65           0 :                           BENCH_MAX_LIVE_SLOTS,
      66           0 :                           max_writes,
      67           0 :                           BENCH_PARTITION_CNT,
      68           0 :                           BENCH_PARTITION_SZ,
      69           0 :                           BENCH_CACHE_FOOTPRINT,
      70           0 :                           BENCH_CACHE_MIN_RESERVED,
      71           0 :                           0,
      72           0 :                           42UL,
      73           0 :                           2UL,
      74           0 :                           0UL ) );
      75           0 :   FD_TEST( shmem );
      76             : 
      77           0 :   ulong accdb_fp = fd_accdb_footprint( BENCH_MAX_LIVE_SLOTS );
      78           0 :   FD_TEST( accdb_fp );
      79           0 :   void * t1_mem = aligned_alloc( fd_accdb_align(), accdb_fp );
      80           0 :   void * t2_mem = aligned_alloc( fd_accdb_align(), accdb_fp );
      81           0 :   FD_TEST( t1_mem );
      82           0 :   FD_TEST( t2_mem );
      83           0 :   fd_accdb_t * t1 = fd_accdb_join( fd_accdb_new( t1_mem, shmem, fd, 0UL, NULL ) );
      84           0 :   fd_accdb_t * t2 = fd_accdb_join( fd_accdb_new( t2_mem, shmem, fd, 0UL, NULL ) );
      85           0 :   FD_TEST( t1 );
      86           0 :   FD_TEST( t2 );
      87             : 
      88           0 :   return (bench_env_t){
      89           0 :     .t1        = t1,
      90           0 :     .t2        = t2,
      91           0 :     .t1_mem    = t1_mem,
      92           0 :     .t2_mem    = t2_mem,
      93           0 :     .shmem_mem = shmem_mem,
      94           0 :     .fd        = fd
      95           0 :   };
      96           0 : }
      97             : 
      98             : static void
      99           0 : bench_teardown( bench_env_t * env ) {
     100           0 :   free( env->shmem_mem );
     101           0 :   free( env->t1_mem );
     102           0 :   free( env->t2_mem );
     103           0 :   close( env->fd );
     104           0 : }
     105             : 
     106             : static void
     107           0 : pin_thread( ulong cpu ) {
     108           0 :   if( cpu==ULONG_MAX ) return;
     109           0 :   FD_TEST( cpu<CPU_SETSIZE );
     110           0 :   cpu_set_t set;
     111           0 :   CPU_ZERO( &set );
     112           0 :   CPU_SET( cpu, &set );
     113           0 :   FD_TEST( !pthread_setaffinity_np( pthread_self(), sizeof(set), &set ) );
     114           0 : }
     115             : 
     116             : static ulong
     117           0 : monotonic_raw( void ) {
     118           0 :   struct timespec ts;
     119           0 :   FD_TEST( !clock_gettime( CLOCK_MONOTONIC_RAW, &ts ) );
     120           0 :   return (ulong)ts.tv_sec*1000000000UL + (ulong)ts.tv_nsec;
     121           0 : }
     122             : 
     123             : typedef struct {
     124             :   fd_accdb_t *    accdb;
     125             :   pthread_t       thread;
     126             :   pthread_mutex_t lock;
     127             :   pthread_cond_t  cond;
     128             :   ulong           cpu;
     129             :   ulong           elapsed;
     130             :   int             request;
     131             :   int             done;
     132             :   int             stop;
     133             : } background_worker_t;
     134             : 
     135             : static void *
     136           0 : background_main( void * _worker ) {
     137           0 :   background_worker_t * worker = _worker;
     138           0 :   pin_thread( worker->cpu );
     139             : 
     140           0 :   FD_TEST( !pthread_mutex_lock( &worker->lock ) );
     141           0 :   for(;;) {
     142           0 :     while( !worker->request && !worker->stop )
     143           0 :       FD_TEST( !pthread_cond_wait( &worker->cond, &worker->lock ) );
     144           0 :     if( worker->stop ) break;
     145           0 :     worker->request = 0;
     146           0 :     FD_TEST( !pthread_mutex_unlock( &worker->lock ) );
     147             : 
     148           0 :     int charge_busy = 0;
     149           0 :     ulong then = monotonic_raw();
     150           0 :     fd_accdb_background( worker->accdb, &charge_busy );
     151           0 :     ulong now = monotonic_raw();
     152           0 :     FD_TEST( charge_busy );
     153             : 
     154           0 :     FD_TEST( !pthread_mutex_lock( &worker->lock ) );
     155           0 :     worker->elapsed = now-then;
     156           0 :     worker->done = 1;
     157           0 :     FD_TEST( !pthread_cond_signal( &worker->cond ) );
     158           0 :   }
     159           0 :   FD_TEST( !pthread_mutex_unlock( &worker->lock ) );
     160           0 :   return NULL;
     161           0 : }
     162             : 
     163             : static void
     164             : background_start( background_worker_t * worker,
     165             :                   fd_accdb_t *          accdb,
     166           0 :                   ulong                 cpu ) {
     167           0 :   *worker = (background_worker_t){
     168           0 :     .accdb = accdb,
     169           0 :     .cpu   = cpu
     170           0 :   };
     171           0 :   FD_TEST( !pthread_mutex_init( &worker->lock, NULL ) );
     172           0 :   FD_TEST( !pthread_cond_init( &worker->cond, NULL ) );
     173           0 :   FD_TEST( !pthread_create( &worker->thread, NULL, background_main, worker ) );
     174           0 : }
     175             : 
     176             : static ulong
     177           0 : background_run( background_worker_t * worker ) {
     178           0 :   FD_TEST( !pthread_mutex_lock( &worker->lock ) );
     179           0 :   FD_TEST( !worker->request );
     180           0 :   FD_TEST( !worker->done );
     181           0 :   worker->request = 1;
     182           0 :   FD_TEST( !pthread_cond_signal( &worker->cond ) );
     183           0 :   while( !worker->done )
     184           0 :     FD_TEST( !pthread_cond_wait( &worker->cond, &worker->lock ) );
     185           0 :   ulong elapsed = worker->elapsed;
     186           0 :   worker->done = 0;
     187           0 :   FD_TEST( !pthread_mutex_unlock( &worker->lock ) );
     188           0 :   return elapsed;
     189           0 : }
     190             : 
     191             : static void
     192           0 : background_stop( background_worker_t * worker ) {
     193           0 :   FD_TEST( !pthread_mutex_lock( &worker->lock ) );
     194           0 :   worker->stop = 1;
     195           0 :   FD_TEST( !pthread_cond_signal( &worker->cond ) );
     196           0 :   FD_TEST( !pthread_mutex_unlock( &worker->lock ) );
     197           0 :   FD_TEST( !pthread_join( worker->thread, NULL ) );
     198           0 :   FD_TEST( !pthread_cond_destroy( &worker->cond ) );
     199           0 :   FD_TEST( !pthread_mutex_destroy( &worker->lock ) );
     200           0 : }
     201             : 
     202             : static void
     203             : make_pubkey( uchar pubkey[ static 32 ],
     204           0 :              ulong idx ) {
     205           0 :   fd_memset( pubkey, 0, 32UL );
     206           0 :   fd_memcpy( pubkey, &idx, sizeof(ulong) );
     207           0 : }
     208             : 
     209             : static void
     210             : write_slot( fd_accdb_t *       accdb,
     211             :             fd_accdb_fork_id_t fork_id,
     212             :             ulong              writes_per_slot,
     213           0 :             ulong              slot ) {
     214           0 :   for( ulong i=0UL; i<writes_per_slot; i++ ) {
     215           0 :     uchar pubkey[ 32 ];
     216           0 :     make_pubkey( pubkey, i );
     217             : 
     218           0 :     uchar const * pubkeys[ 1 ] = { pubkey };
     219           0 :     int writable[ 1 ] = { 1 };
     220           0 :     fd_acc_t acc[ 1 ];
     221           0 :     fd_memset( acc, 0, sizeof(acc) );
     222           0 :     fd_accdb_acquire( accdb, fork_id, 1UL, pubkeys, writable, acc );
     223           0 :     acc[ 0 ].lamports = slot + 1UL;
     224           0 :     acc[ 0 ].data_len = 0UL;
     225           0 :     fd_memset( acc[ 0 ].owner, 0, sizeof(acc[ 0 ].owner) );
     226           0 :     acc[ 0 ].owner[ 0 ] = 1U;
     227           0 :     acc[ 0 ].commit = 1;
     228           0 :     fd_accdb_release( accdb, 1UL, acc );
     229           0 :   }
     230           0 : }
     231             : 
     232             : static int
     233             : cmp_ulong( void const * _a,
     234           0 :            void const * _b ) {
     235           0 :   ulong a = *(ulong const *)_a;
     236           0 :   ulong b = *(ulong const *)_b;
     237           0 :   return (a>b) - (a<b);
     238           0 : }
     239             : 
     240             : static void
     241             : report( char const * mode,
     242             :         ulong        writes_per_slot,
     243             :         ulong        warmup_cnt,
     244             :         ulong        sample_cnt,
     245           0 :         ulong *      samples ) {
     246           0 :   double sum = 0.0;
     247           0 :   for( ulong i=0UL; i<sample_cnt; i++ ) sum += (double)samples[ i ];
     248           0 :   qsort( samples, sample_cnt, sizeof(ulong), cmp_ulong );
     249             : 
     250           0 :   ulong rank50 = (sample_cnt/100UL)*50UL + ((sample_cnt%100UL)*50UL+99UL)/100UL;
     251           0 :   ulong rank95 = (sample_cnt/100UL)*95UL + ((sample_cnt%100UL)*95UL+99UL)/100UL;
     252           0 :   ulong p50 = samples[ rank50-1UL ];
     253           0 :   ulong p95 = samples[ rank95-1UL ];
     254           0 :   double mean = sum/(double)sample_cnt;
     255             : 
     256           0 :   FD_LOG_NOTICE(( "%s: writes/slot=%lu warmup=%lu samples=%lu", mode, writes_per_slot, warmup_cnt, sample_cnt ));
     257           0 :   FD_LOG_NOTICE(( "  mean %.0f ns  p50 %lu ns  p95 %lu ns", mean, p50, p95 ));
     258           0 :   FD_LOG_NOTICE(( "  mean %.2f ns/write  p50 %.2f ns/write  p95 %.2f ns/write",
     259           0 :                   mean/(double)writes_per_slot,
     260           0 :                   (double)p50/(double)writes_per_slot,
     261           0 :                   (double)p95/(double)writes_per_slot ));
     262           0 : }
     263             : 
     264             : static void
     265             : bench_root( ulong writes_per_slot,
     266             :             ulong warmup_cnt,
     267             :             ulong sample_cnt,
     268           0 :             ulong t2_cpu ) {
     269           0 :   bench_env_t env = bench_setup( writes_per_slot );
     270           0 :   ulong * samples = malloc( sample_cnt*sizeof(ulong) );
     271           0 :   FD_TEST( samples );
     272           0 :   background_worker_t worker[ 1 ];
     273           0 :   background_start( worker, env.t2, t2_cpu );
     274             : 
     275           0 :   fd_accdb_fork_id_t root = fd_accdb_attach_child(
     276           0 :       env.t1, (fd_accdb_fork_id_t){ .val = USHORT_MAX } );
     277           0 :   write_slot( env.t1, root, writes_per_slot, 0UL );
     278             : 
     279           0 :   fd_accdb_fork_id_t prev = fd_accdb_attach_child( env.t1, root );
     280           0 :   write_slot( env.t1, prev, writes_per_slot, 1UL );
     281             : 
     282             :   /* Prime the old-root and deferred-free paths before any samples. */
     283           0 :   fd_accdb_fork_id_t cur = fd_accdb_attach_child( env.t1, prev );
     284           0 :   write_slot( env.t1, cur, writes_per_slot, 2UL );
     285           0 :   fd_accdb_advance_root( env.t1, prev );
     286           0 :   (void)background_run( worker );
     287           0 :   prev = cur;
     288             : 
     289           0 :   ulong iter_cnt = warmup_cnt + sample_cnt;
     290           0 :   for( ulong iter=0UL; iter<iter_cnt; iter++ ) {
     291           0 :     cur = fd_accdb_attach_child( env.t1, prev );
     292           0 :     write_slot( env.t1, cur, writes_per_slot, iter+3UL );
     293             : 
     294           0 :     fd_accdb_advance_root( env.t1, prev );
     295           0 :     ulong elapsed = background_run( worker );
     296           0 :     if( iter>=warmup_cnt ) samples[ iter-warmup_cnt ] = elapsed;
     297           0 :     prev = cur;
     298           0 :   }
     299             : 
     300           0 :   report( "root", writes_per_slot, warmup_cnt, sample_cnt, samples );
     301           0 :   background_stop( worker );
     302           0 :   free( samples );
     303           0 :   bench_teardown( &env );
     304           0 : }
     305             : 
     306             : static void
     307             : bench_purge( ulong writes_per_slot,
     308             :              ulong warmup_cnt,
     309             :              ulong sample_cnt,
     310           0 :              ulong t2_cpu ) {
     311           0 :   bench_env_t env = bench_setup( writes_per_slot );
     312           0 :   ulong * samples = malloc( sample_cnt*sizeof(ulong) );
     313           0 :   FD_TEST( samples );
     314           0 :   background_worker_t worker[ 1 ];
     315           0 :   background_start( worker, env.t2, t2_cpu );
     316             : 
     317           0 :   fd_accdb_fork_id_t root = fd_accdb_attach_child(
     318           0 :       env.t1, (fd_accdb_fork_id_t){ .val = USHORT_MAX } );
     319           0 :   write_slot( env.t1, root, writes_per_slot, 0UL );
     320             : 
     321             :   /* Prime deferred reclamation before any samples. */
     322           0 :   fd_accdb_fork_id_t fork = fd_accdb_attach_child( env.t1, root );
     323           0 :   write_slot( env.t1, fork, writes_per_slot, 1UL );
     324           0 :   fd_accdb_purge( env.t1, fork );
     325           0 :   (void)background_run( worker );
     326             : 
     327           0 :   ulong iter_cnt = warmup_cnt + sample_cnt;
     328           0 :   for( ulong iter=0UL; iter<iter_cnt; iter++ ) {
     329           0 :     fork = fd_accdb_attach_child( env.t1, root );
     330           0 :     write_slot( env.t1, fork, writes_per_slot, iter+2UL );
     331             : 
     332           0 :     fd_accdb_purge( env.t1, fork );
     333           0 :     ulong elapsed = background_run( worker );
     334           0 :     if( iter>=warmup_cnt ) samples[ iter-warmup_cnt ] = elapsed;
     335           0 :   }
     336             : 
     337           0 :   report( "purge", writes_per_slot, warmup_cnt, sample_cnt, samples );
     338           0 :   background_stop( worker );
     339           0 :   free( samples );
     340           0 :   bench_teardown( &env );
     341           0 : }
     342             : 
     343             : int
     344             : main( int     argc,
     345             :       char ** argv ) {
     346             :   fd_boot( &argc, &argv );
     347             : 
     348             :   char const * mode = fd_env_strip_cmdline_cstr( &argc, &argv, "--mode", NULL, "both" );
     349             :   ulong writes_per_slot = fd_env_strip_cmdline_ulong( &argc, &argv, "--writes-per-slot", NULL, 10000UL );
     350             :   ulong warmup_cnt       = fd_env_strip_cmdline_ulong( &argc, &argv, "--warmup",         NULL,    10UL );
     351             :   ulong sample_cnt       = fd_env_strip_cmdline_ulong( &argc, &argv, "--samples",        NULL,    50UL );
     352             :   ulong t1_cpu           = fd_env_strip_cmdline_ulong( &argc, &argv, "--t1-cpu",         NULL, ULONG_MAX );
     353             :   ulong t2_cpu           = fd_env_strip_cmdline_ulong( &argc, &argv, "--t2-cpu",         NULL, ULONG_MAX );
     354             : 
     355             :   FD_TEST( writes_per_slot );
     356             :   FD_TEST( sample_cnt );
     357             :   FD_TEST( sample_cnt<=ULONG_MAX/sizeof(ulong) );
     358             :   FD_TEST( warmup_cnt<=ULONG_MAX-sample_cnt );
     359             :   pin_thread( t1_cpu );
     360             : 
     361             :   if( !strcmp( mode, "root" ) ) {
     362             :     bench_root( writes_per_slot, warmup_cnt, sample_cnt, t2_cpu );
     363             :   } else if( !strcmp( mode, "purge" ) ) {
     364             :     bench_purge( writes_per_slot, warmup_cnt, sample_cnt, t2_cpu );
     365             :   } else if( !strcmp( mode, "both" ) ) {
     366             :     bench_root ( writes_per_slot, warmup_cnt, sample_cnt, t2_cpu );
     367             :     bench_purge( writes_per_slot, warmup_cnt, sample_cnt, t2_cpu );
     368             :   } else {
     369             :     FD_LOG_ERR(( "unsupported --mode %s (expected root, purge, or both)", mode ));
     370             :   }
     371             : 
     372             :   FD_LOG_NOTICE(( "pass" ));
     373             :   fd_halt();
     374             :   return 0;
     375             : }

Generated by: LCOV version 1.14