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

          Line data    Source code
       1             : #include "fd_accdb_cache.h"
       2             : 
       3             : #include "../../util/bits/fd_bits.h"
       4             : #include "../../util/log/fd_log.h"
       5             : 
       6             : int
       7             : fd_accdb_cache_class_cnt( ulong   cache_footprint,
       8             :                           ulong   min_reserved,
       9        4044 :                           ulong * class_cnt ) {
      10             :   /* Estimated max account population per class on mainnet.  Based on a
      11             :      full mainnet snapshot (1.118B accounts, 363.7 GiB, slot 393863972)
      12             :      with ~20% headroom. */
      13             : 
      14        4044 :   static const ulong pop_max_raw[ FD_ACCDB_CACHE_CLASS_CNT ] = {
      15        4044 :      215000000UL,  /* class 0: ~16% of accounts   */
      16        4044 :     1041000000UL,  /* class 1: ~77.6% of accounts */
      17        4044 :       76000000UL,  /* class 2: ~5.6%              */
      18        4044 :        8000000UL,  /* class 3: ~0.6%              */
      19        4044 :        4000000UL,  /* class 4: ~0.3%              */
      20        4044 :         461000UL,  /* class 5: ~0.03%             */
      21        4044 :         244000UL,  /* class 6: ~0.02%             */
      22        4044 :           5000UL,  /* class 7: ~0.0003%           */
      23        4044 :   };
      24             : 
      25             :   /* Hard per-class ceiling: cidx packs only FD_ACCDB_CACHE_LINE_BITS
      26             :      bits of line index, so a class with more than
      27             :      FD_ACCDB_CACHE_LINE_MAX slots would let two distinct lines pack to
      28             :      the same cidx and silently alias on read.  Clamp pop_max[c] to that
      29             :      representable bound before any phase of the allocator consults it.  */
      30        4044 :   ulong pop_max[ FD_ACCDB_CACHE_CLASS_CNT ];
      31       36396 :   for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ ) {
      32       32352 :     pop_max[c] = fd_ulong_min( pop_max_raw[c], FD_ACCDB_CACHE_LINE_MAX );
      33       32352 :   }
      34             : 
      35             :   /* Access density weights: total_accesses / slot_size from empirical
      36             :      mainnet replay (1000-slot sample at slot 406546575), adjusted for
      37             :      64-byte header offset when mapping data_sz to stored_sz cache
      38             :      classes.  Higher weight means more cache hits per byte of cache
      39             :      spent.  Classes 6, 7 are floored to 1. */
      40             : 
      41        4044 :   static const ulong density[ FD_ACCDB_CACHE_CLASS_CNT ] = {
      42        4044 :        25UL,  /* class 0 */
      43        4044 :        20UL,  /* class 1 */
      44        4044 :        12UL,  /* class 2 */
      45        4044 :         7UL,  /* class 3 */
      46        4044 :         6UL,  /* class 4 */
      47        4044 :         3UL,  /* class 5 */
      48        4044 :         1UL,  /* class 6 */
      49        4044 :         1UL,  /* class 7 */
      50        4044 :   };
      51             : 
      52             :   /* Per-class working-set targets (slot counts).  Derived from p99 of
      53             :      the distinct-pubkey-per-class working set measured over a 32-slot
      54             :      sliding window on the same 1000-slot mainnet sample, with ~25-50%
      55             :      headroom so allocations cover the typical hot set without wasting
      56             :      budget on classes whose live working set is tiny.
      57             : 
      58             :      These are floors used by Phase 2: each class is topped up to
      59             :      ws_target[c] above the Phase 1 base reservation, before
      60             :      density-based distribution.  Phase 3 then distributes any remaining
      61             :      budget by density. */
      62             : 
      63        4044 :   static const ulong ws_target[ FD_ACCDB_CACHE_CLASS_CNT ] = {
      64        4044 :     16384UL,  /* class 0: p99 ~13.4K, ample headroom (small slots) */
      65        4044 :     13000UL,  /* class 1: p99 ~10.4K */
      66        4044 :      4096UL,  /* class 2: p99  ~3.2K */
      67        4044 :      3072UL,  /* class 3: p99  ~2.0K, was undersized at 1.3K */
      68        4044 :      1800UL,  /* class 4: p99  ~1.0K, needs headroom for pre-evict to keep up */
      69        4044 :       512UL,  /* class 5: p99    ~66, was wastefully sized at 1.3K */
      70        4044 :       704UL,  /* class 6: p99   ~212 */
      71        4044 :       544UL,  /* class 7: p99   ~179; staging covered by MIN_RESERVED */
      72        4044 :   };
      73             : 
      74        4044 :   ulong slot_sz_sum = ( fd_accdb_cache_slot_sz[ 0UL ] +
      75        4044 :                         fd_accdb_cache_slot_sz[ 1UL ] +
      76        4044 :                         fd_accdb_cache_slot_sz[ 2UL ] +
      77        4044 :                         fd_accdb_cache_slot_sz[ 3UL ] +
      78        4044 :                         fd_accdb_cache_slot_sz[ 4UL ] +
      79        4044 :                         fd_accdb_cache_slot_sz[ 5UL ] +
      80        4044 :                         fd_accdb_cache_slot_sz[ 6UL ] +
      81        4044 :                         fd_accdb_cache_slot_sz[ 7UL ] );
      82             : 
      83             :   /* min_reserved is a runtime parameter; guard the Phase-1 cost
      84             :      multiplication against overflow before it wraps and makes the
      85             :      "budget too small" check below misbehave (a wrapped small value
      86             :      would pass the check and yield bogus class counts). */
      87        4044 :   if( FD_UNLIKELY( min_reserved>ULONG_MAX/slot_sz_sum ) ) {
      88           0 :     FD_LOG_WARNING(( "cache_min_reserved %lu too large (overflows minimum cost)", min_reserved ));
      89           0 :     for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ ) class_cnt[ c ] = 0UL;
      90           0 :     return 0;
      91           0 :   }
      92             : 
      93        4044 :   ulong minimum_cost = min_reserved * slot_sz_sum;
      94             : 
      95        4044 :   if( FD_UNLIKELY( cache_footprint<minimum_cost ) ) {
      96             :     /* Budget too small to meet minimum requirement.  Return 0 to
      97             :        indicate failure. */
      98          12 :     FD_LOG_WARNING(( "cache_footprint must be at least %lu GiB to meet minimum requirements", (minimum_cost+(1UL<<30UL)-1)/(1UL<<30UL) ));
      99          12 :     FD_LOG_WARNING(( "%lu<%lu", cache_footprint, minimum_cost ));
     100         108 :     for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ ) class_cnt[ c ] = 0UL;
     101          12 :     return 0;
     102          12 :   }
     103             : 
     104             :   /* Phase 1: Reserve min_reserved of each class off the top.  This
     105             :      guarantees the worst-case batch (64 accounts per transaction,
     106             :      doubled to cover programdata, multiplied by max simultaneous
     107             :      transactions) can execute fully in memory.  Each referenced account
     108             :      reserves one slot in its own class plus one slot for its
     109             :      programdata account, which may land in any class.  Worst case all
     110             :      referenced accounts and all programdata accounts land in the same
     111             :      class. */
     112             : 
     113        4032 :   ulong remaining = cache_footprint;
     114       36288 :   for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ ) {
     115       32256 :     class_cnt[c] = min_reserved;
     116       32256 :     remaining   -= min_reserved * fd_accdb_cache_slot_sz[c];
     117       32256 :   }
     118             : 
     119             :   /* Phase 2: Reserve up to ws_target[c] slots per class as a floor.
     120             :      Phase 1 already gave each class min_reserved slots; here we top up
     121             :      to ws_target[c] (or as much as remaining budget allows).  This
     122             :      keeps tiny working sets (128K/1M/10M classes) from being
     123             :      over-allocated and frees budget for hotter classes (8K, 2K) in
     124             :      Phase 3. */
     125             : 
     126       36288 :   for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ ) {
     127       32256 :     if( ws_target[c]<=class_cnt[c] ) continue;
     128       32172 :     ulong want = ws_target[c] - class_cnt[c];
     129       32172 :     want = fd_ulong_min( want, pop_max[c]>class_cnt[c] ? pop_max[c]-class_cnt[c] : 0UL );
     130       32172 :     ulong cost = want * fd_accdb_cache_slot_sz[c];
     131       32172 :     if( FD_UNLIKELY( cost>remaining ) ) {
     132        5070 :       class_cnt[c] += remaining / fd_accdb_cache_slot_sz[c];
     133        5070 :       remaining     = 0UL;
     134       27102 :     } else {
     135       27102 :       class_cnt[c] += want;
     136       27102 :       remaining    -= cost;
     137       27102 :     }
     138       32172 :   }
     139             : 
     140             :   /* Phase 3: Iteratively allocate remaining budget proportional
     141             :      to access density.  When a class exceeds its population cap,
     142             :      freeze it and redistribute surplus to uncapped classes. */
     143             : 
     144        4032 :   int capped[ FD_ACCDB_CACHE_CLASS_CNT ];
     145       36288 :   for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ ) capped[c] = 0;
     146             : 
     147        4074 :   for( ulong iter=0UL; iter<FD_ACCDB_CACHE_CLASS_CNT && remaining; iter++ ) {
     148          84 :     ulong total_w = 0UL;
     149         756 :     for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ )
     150         672 :       if( !capped[c] ) total_w += density[c];
     151          84 :     if( FD_UNLIKELY( !total_w ) ) break;
     152             : 
     153          75 :     int any_capped = 0;
     154         675 :     for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ ) {
     155         600 :       if( capped[c] ) continue;
     156         459 :       ulong budget = remaining * density[c] / total_w;
     157         459 :       ulong extra  = budget / fd_accdb_cache_slot_sz[c];
     158         459 :       if( class_cnt[c]+extra >= pop_max[c] ) {
     159          81 :         ulong added  = pop_max[c] - class_cnt[c];
     160          81 :         class_cnt[c] = pop_max[c];
     161          81 :         remaining   -= added * fd_accdb_cache_slot_sz[c];
     162          81 :         capped[c]    = 1;
     163          81 :         any_capped   = 1;
     164          81 :       }
     165         459 :     }
     166             : 
     167          75 :     if( !any_capped ) {
     168             :       /* No caps hit.  Final proportional allocation. */
     169          33 :       total_w = 0UL;
     170         297 :       for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ )
     171         264 :         if( !capped[c] ) total_w += density[c];
     172          33 :       if( FD_UNLIKELY( !total_w ) ) break;
     173         297 :       for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ ) {
     174         264 :         if( capped[c] ) continue;
     175         255 :         ulong budget = remaining * density[c] / total_w;
     176         255 :         class_cnt[c] += budget / fd_accdb_cache_slot_sz[c];
     177         255 :       }
     178          33 :       break;
     179          33 :     }
     180          75 :   }
     181             : 
     182             :   /* Phase 4: If all classes hit their population caps, there may
     183             :      still be remaining budget.  The accounts database can grow at
     184             :      runtime, so distribute excess uncapped, proportional to
     185             :      density.  This ensures we always use the full cache budget
     186             :      the operator gave us, up to the per-class cidx ceiling
     187             :      (FD_ACCDB_CACHE_LINE_MAX, enforced via the capped[] array). */
     188             : 
     189        4032 :   remaining = cache_footprint;
     190       36288 :   for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ )
     191       32256 :     remaining -= class_cnt[c] * fd_accdb_cache_slot_sz[c];
     192             : 
     193       36288 :   for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ ) capped[c] = class_cnt[c]>=FD_ACCDB_CACHE_LINE_MAX;
     194             : 
     195        4038 :   for( ulong iter=0UL; iter<FD_ACCDB_CACHE_CLASS_CNT && remaining; iter++ ) {
     196        3915 :     ulong total_w = 0UL;
     197       35235 :     for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ ) {
     198       31320 :       if( !capped[c] ) total_w += density[c];
     199       31320 :     }
     200        3915 :     if( FD_UNLIKELY( !total_w ) ) break;
     201             : 
     202        3915 :     int any_capped = 0;
     203       35235 :     for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ ) {
     204       31320 :       if( capped[c] ) continue;
     205       31299 :       ulong budget = remaining * density[c] / total_w;
     206       31299 :       ulong extra  = budget / fd_accdb_cache_slot_sz[c];
     207       31299 :       if( class_cnt[c]+extra >= FD_ACCDB_CACHE_LINE_MAX ) {
     208           6 :         ulong added  = FD_ACCDB_CACHE_LINE_MAX - class_cnt[c];
     209           6 :         class_cnt[c] = FD_ACCDB_CACHE_LINE_MAX;
     210           6 :         remaining   -= added * fd_accdb_cache_slot_sz[c];
     211           6 :         capped[c]    = 1;
     212           6 :         any_capped   = 1;
     213           6 :       }
     214       31299 :     }
     215             : 
     216        3915 :     if( !any_capped ) {
     217        3909 :       total_w = 0UL;
     218       35181 :       for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ ) {
     219       31272 :         if( !capped[c] ) total_w += density[c];
     220       31272 :       }
     221        3909 :       if( FD_UNLIKELY( !total_w ) ) break;
     222       35181 :       for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ ) {
     223       31272 :         if( capped[c] ) continue;
     224       31257 :         ulong budget = remaining * density[c] / total_w;
     225       31257 :         ulong extra  = budget / fd_accdb_cache_slot_sz[c];
     226       31257 :         extra        = fd_ulong_min( extra, FD_ACCDB_CACHE_LINE_MAX - class_cnt[c] );
     227       31257 :         class_cnt[c] += extra;
     228       31257 :         remaining    -= extra * fd_accdb_cache_slot_sz[c];
     229       31257 :       }
     230        3909 :       break;
     231        3909 :     }
     232        3915 :   }
     233             : 
     234             :   /* Spend any per-class integer-division truncation slack left after
     235             :      Phase 4.  Walk uncapped classes in descending density order and
     236             :      add as many slots as fit, capped at FD_ACCDB_CACHE_LINE_MAX.  This
     237             :      converges in at most one pass per class because each iteration
     238             :      either fills the class to LINE_MAX or drains remaining below
     239             :      slot_sz[c]. */
     240       35304 :   for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT && remaining; c++ ) {
     241       31272 :     if( class_cnt[c]>=FD_ACCDB_CACHE_LINE_MAX ) continue;
     242       31257 :     ulong room  = FD_ACCDB_CACHE_LINE_MAX - class_cnt[c];
     243       31257 :     ulong extra = fd_ulong_min( room, remaining / fd_accdb_cache_slot_sz[c] );
     244       31257 :     class_cnt[c] += extra;
     245       31257 :     remaining    -= extra * fd_accdb_cache_slot_sz[c];
     246       31257 :   }
     247             : 
     248             :   /* Past FD_ACCDB_CACHE_LINE_MAX*slot_sz[c] (~6 TiB) the index space is
     249             :      fully saturated and any further budget is unspendable on cache
     250             :      lines.  Warn the operator so they can size down. */
     251        4032 :   ulong unspent = cache_footprint;
     252       36288 :   for( ulong c=0UL; c<FD_ACCDB_CACHE_CLASS_CNT; c++ )
     253       32256 :     unspent -= class_cnt[c] * fd_accdb_cache_slot_sz[c];
     254        4032 :   if( FD_UNLIKELY( unspent >= fd_accdb_cache_slot_sz[ 0UL ] ) ) {
     255           6 :     FD_LOG_WARNING(( "cache_footprint exceeds per-class index space; %lu GiB will be unused (raise FD_ACCDB_CACHE_LINE_BITS to use more)",
     256           6 :                      (unspent+(1UL<<30UL)-1UL)/(1UL<<30UL) ));
     257           6 :   }
     258             : 
     259        4032 :   return 1;
     260        4044 : }
     261             : 
     262             : ulong
     263      444114 : fd_accdb_cache_class( ulong data_sz ) {
     264      444114 :   if( FD_LIKELY( data_sz<=128UL ) ) return 0UL;
     265      178005 :   else if( FD_LIKELY( data_sz<=512UL ) ) return 1UL;
     266      162405 :   else if( FD_LIKELY( data_sz<=2048UL ) ) return 2UL;
     267      162105 :   else if( FD_LIKELY( data_sz<=8192UL ) ) return 3UL;
     268      119016 :   else if( FD_LIKELY( data_sz<=32768UL ) ) return 4UL;
     269       31080 :   else if( FD_LIKELY( data_sz<=131072UL ) ) return 5UL;
     270       31080 :   else if( FD_LIKELY( data_sz<=1048576UL ) ) return 6UL;
     271        1626 :   return 7UL;
     272      444114 : }

Generated by: LCOV version 1.14