LCOV - code coverage report
Current view: top level - discof/replay - fd_rdisp_mq.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 61 112 54.5 %
Date: 2026-11-11 12:40:43 Functions: 8 26 30.8 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_discof_replay_fd_rdisp_mq_h
       2             : #define HEADER_fd_src_discof_replay_fd_rdisp_mq_h
       3             : 
       4             : /* The multiprq (mq) is a strange combination of data structures
       5             :    specifically for use in fd_rdisp.  It's factored out into its own
       6             :    file to abstract the details away and to facilitate testing.  It
       7             :    supports 4 priority queues (corresponding to the 4 staging lanes),
       8             :    with each prq also having an optimization for top priority (prio=0)
       9             :    items.  For each element (acct_idx, staging_lane_idx) it supports the
      10             :    following operations:
      11             :       * Is the element in the structure?
      12             :       * Insert the element with a specific priority
      13             :       * Adjust the element's priority
      14             :    Additionally, for a staging_lane_idx, it supports:
      15             :       * Return the element with the best priority (lowest value) and
      16             :         remove it from the structure.
      17             :       * Count the number of elements in the structure with the specified
      18             :         lane index.
      19             : 
      20             :    For each element, we require acct_idx in [1, acct_depth] and
      21             :    staging_lane_idx in [0, 4).  Additionally, at most writers_per_block
      22             :    elements can be in the structure for each staging_lane_idx at a time.
      23             :    prio>=0 with 0 being the highest priority item. */
      24             : 
      25             : struct fd_rdisp_mq_private;
      26             : typedef struct fd_rdisp_mq_private fd_rdisp_mq_t;
      27             : 
      28             : FD_PROTOTYPES_BEGIN
      29             : 
      30             : /* fd_rdisp_mq_{align,footprint} return the required alignment and
      31             :    footprint in bytes of a region of memory to be formatted to be used
      32             :    as a multiprq.  acct_depth is the maximum acct_idx (inclusive) that
      33             :    can be used for an element, and max_writers_per_block elements can be
      34             :    tracked in each staging lane.  acct_depth in range [1, UINT_MAX] and
      35             :    max_writers_per_block in range [1, UINT_MAX-1].  If
      36             :    max_writers_per_block > acct_depth, the effective value used will be
      37             :    acct_depth instead. */
      38       18056 : static inline ulong fd_rdisp_mq_align( void ) { return 8UL; }
      39             : static inline ulong fd_rdisp_mq_footprint( ulong acct_depth, ulong max_writers_per_block );
      40             : 
      41             : /* fd_rdisp_mq_new formats a region of memory with the required
      42             :    footprint and depth for use as a multiprq.  acct_depth and
      43             :    max_writers_per_block are as in footprint.  The caller will not be
      44             :    joined upon return.
      45             : 
      46             :    fd_rdisp_mq_join joins the caller to the multiprq, making it ready
      47             :    for use. */
      48             : static inline void *
      49             : fd_rdisp_mq_new( void * mem,
      50             :                  ulong acct_depth,
      51             :                  ulong max_writers_per_block );
      52             : 
      53             : static inline fd_rdisp_mq_t * fd_rdisp_mq_join( void * mem );
      54             : 
      55             : /* fd_rdisp_mq_test returns 1 if the element (acct_idx, staging_lane)
      56             :    is in mq, and 0 otherwise.  acct_idx and staging_lane must be in the
      57             :    requisite range and mq must be a valid local join.  An element is in
      58             :    the multiprq if it has been inserted one more time than it has been
      59             :    popped. */
      60             : static inline int
      61             : fd_rdisp_mq_test( fd_rdisp_mq_t const * mq,
      62             :                   ulong                 acct_idx,
      63             :                   ulong                 staging_lane );
      64             : 
      65             : /* fd_rdisp_mq_insert inserts the element (acct_idx, staging_lane) into
      66             :    the multiprq with priority prio.  The element must not be in the
      67             :    multiprq prior to this call.  prio>=0, with lower values getting
      68             :    popped first. */
      69             : static inline void
      70             : fd_rdisp_mq_insert( fd_rdisp_mq_t * mq,
      71             :                     ulong           acct_idx,
      72             :                     ulong           staging_lane,
      73             :                     float           prio );
      74             : 
      75             : /* fd_rdisp_mq_adjust changes the priority of the specified element
      76             :    (acct_idx, staging_lane) to prio.  The element must be in the
      77             :    multiprq prior to this call and remains so after the call.  prio>=0.
      78             :    This is especially optimized for the prio==0 case. */
      79             : static inline void
      80             : fd_rdisp_mq_adjust( fd_rdisp_mq_t * mq,
      81             :                     ulong           acct_idx,
      82             :                     ulong           staging_lane,
      83             :                     float           prio );
      84             : 
      85             : /* fd_rdisp_mq_pop returns and removes the best priority element with
      86             :    the specified staging lane.  The caller promises that there is at
      87             :    least one element in the multiprq with the specified staging lane.
      88             :    Ties are broken arbitrarily. */
      89             : static inline ulong
      90             : fd_rdisp_mq_pop( fd_rdisp_mq_t * mq,
      91             :                  ulong           staging_lane );
      92             : 
      93             : /* fd_rdisp_mq_cnt returns the number of elements in the multiprq with
      94             :    specified staging lane. */
      95             : static inline ulong
      96             : fd_rdisp_mq_cnt( fd_rdisp_mq_t * mq,
      97             :                  ulong           staging_lane );
      98             : 
      99             : /* fd_rdisp_mq_prio0_cnt returns the number of elements in the multiprq
     100             :    with the specified staging lane that have priority 0. */
     101             : static inline ulong
     102             : fd_rdisp_mq_prio0_cnt( fd_rdisp_mq_t const * mq,
     103             :                        ulong                 staging_lane );
     104             : 
     105             : /* fd_rdisp_mq_test_prio0 returns 1 if the element
     106             :    (acct_idx,staging_lane) has priority 0, and 0 otherwise.  The element
     107             :    must be in the multiprq. */
     108             : static inline int
     109             : fd_rdisp_mq_test_prio0( fd_rdisp_mq_t const * mq,
     110             :                         ulong                 acct_idx,
     111             :                         ulong                 staging_lane );
     112             : 
     113             : static inline void *
     114             : fd_rdisp_mq_leave( fd_rdisp_mq_t * mq );
     115             : 
     116             : static inline void *
     117             : fd_rdisp_mq_delete( void * mem );
     118             : 
     119             : /* Implementation details below.  These would be in a .c file, but they
     120             :    are mostly pretty quick functions, such that inlining is probably
     121             :    a significant performance win.
     122             : 
     123             :    Probably the most straightforward implementation would be some kind
     124             :    of pool-based heap, with a map.  However, the current pool-based heap
     125             :    does not allow deleting or changing priorities because it does not
     126             :    contain a parent pointer.  Rather than that, we use fd_prq with an
     127             :    inverse index that gets updated as elements get moved.  This leads to
     128             :    some strange macros, but they can be isolated to this file and
     129             :    tested. */
     130             : 
     131             : 
     132             : struct fd_rdisp_mq_4u {
     133             :   /* The current position in prq, or UINT_MAX if it's not present. */
     134             :   uint per_lane[4];
     135             : };
     136             : typedef struct fd_rdisp_mq_4u fd_rdisp_mq_4u_t;
     137             : 
     138             : struct fd_rdisp_mq_prqe {
     139             :   uint  idx;
     140             :   float prio;
     141             : };
     142             : typedef struct fd_rdisp_mq_prqe fd_rdisp_mq_prqe_t;
     143             : 
     144             : 
     145             : /* In this macro, heap (which is defined each place the macro expands)
     146             :    is a pointer to the field with offset 16B from private_t.  The field
     147             :    before the private_t struct is a pointer to the start of the 4u_t
     148             :    array, with 4B*staging lane added. */
     149        2888 : #define PRQ_TMP_ST(p, t) do {                                                                 \
     150        2888 :     (p)[0] = (t);                                                                             \
     151        2888 :     ((fd_rdisp_mq_4u_t *)((void * const *)heap)[-3])[(t).idx].per_lane[0] = (uint)((p)-heap); \
     152        2888 :   } while(0)
     153             : #define PRQ_NAME      fd_rdisp_mprq
     154        4806 : #define PRQ_T         fd_rdisp_mq_prqe_t
     155        4806 : #define PRQ_TIMEOUT_T float
     156        4806 : #define PRQ_TIMEOUT   prio
     157             : #include "../../util/tmpl/fd_prq.c"
     158             : /* As an optimization, we store the prio0 elements at the end of the
     159             :    heap instead of in the heap.  prq allocates 2 dummy elements at the
     160             :    end that are read but not written to, so we'll store the prio0
     161             :    elements there.
     162             : 
     163             :    Elements [0, prq_cnt) are handled by the prq
     164             :    Elements (max_writers_per_block-prio0_cnt, max_writers_per_block]
     165             :    are in the prio0 list.
     166             : 
     167             :    There's always one element in between the lists, since
     168             :    max_writers_per_block-prio0_cnt-prq_cnt >=0. */
     169             : 
     170             : FD_STATIC_ASSERT( sizeof(fd_rdisp_mprq_private_t )==2*sizeof(ulong)+sizeof(fd_rdisp_mq_prqe_t), "prq details" );
     171             : FD_STATIC_ASSERT( alignof(fd_rdisp_mprq_private_t)==8UL,                                        "prq details" );
     172             : 
     173             : struct fd_rdisp_mq_private {
     174             :   ulong acct_depth;
     175             :   ulong max_writers_per_block;
     176             :   ulong prio0_cnt[4];
     177             : 
     178             :   fd_rdisp_mq_4u_t *   flat; /* indexed [0, acct_depth+1) */
     179             :   fd_rdisp_mq_prqe_t * prq[4];
     180             :   /* Then follows:
     181             :      the footprint for flat
     182             :      for i in [0, 4)
     183             :         a pointer to flat[0].per_lane[i]
     184             :         the footprint for prq[i]
     185             :    */
     186             : };
     187             : typedef struct fd_rdisp_mq_private fd_rdisp_mq_t;
     188             : 
     189             : static inline ulong
     190             : fd_rdisp_mq_footprint( ulong acct_depth,
     191        5031 :                        ulong max_writers_per_block ) {
     192             :   /* The normal case is that max_writers_per_block < acct_depth, so this
     193             :      is normally a no-op. */
     194        5031 :   max_writers_per_block = fd_ulong_min( max_writers_per_block, acct_depth );
     195        5031 :   ulong l = FD_LAYOUT_INIT;
     196        5031 :   l = FD_LAYOUT_APPEND( l, alignof(fd_rdisp_mq_t),                     sizeof(fd_rdisp_mq_t)            );
     197        5031 :   l = FD_LAYOUT_APPEND( l, alignof(fd_rdisp_mq_4u_t), (acct_depth+1UL)*sizeof(fd_rdisp_mq_4u_t)         );
     198        5031 :   l = FD_LAYOUT_APPEND( l, 8UL, 4UL*(sizeof(void *)+fd_rdisp_mprq_footprint( max_writers_per_block )) );
     199        5031 :   return FD_LAYOUT_FINI( l, fd_rdisp_mq_align() );
     200        5031 : }
     201             : 
     202             : static inline void *
     203             : fd_rdisp_mq_new( void * mem,
     204             :                  ulong acct_depth,
     205        1008 :                  ulong max_writers_per_block ) {
     206        1008 :   max_writers_per_block = fd_ulong_min( max_writers_per_block, acct_depth );
     207             : 
     208        1008 :   FD_SCRATCH_ALLOC_INIT( l, mem );
     209        1008 :   fd_rdisp_mq_t *    mq = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_rdisp_mq_t),    sizeof(fd_rdisp_mq_t)                     );
     210        1008 :   fd_rdisp_mq_4u_t * u4 = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_rdisp_mq_4u_t), (acct_depth+1UL)*sizeof(fd_rdisp_mq_4u_t) );
     211             : 
     212           0 :   mq->acct_depth = acct_depth;
     213        1008 :   mq->max_writers_per_block = max_writers_per_block;
     214        1008 :   memset( mq->prio0_cnt, '\0', 4UL*sizeof(ulong) );
     215             : 
     216        1008 :   memset( u4, '\xFF', (acct_depth+1UL)*sizeof(fd_rdisp_mq_4u_t) );
     217             : 
     218        5040 :   for( ulong i=0UL; i<4UL; i++ ) {
     219        4032 :     void * _pq = FD_SCRATCH_ALLOC_APPEND( l, 8UL, sizeof(void *)+fd_rdisp_mprq_footprint( max_writers_per_block ) );
     220           0 :     fd_rdisp_mprq_new( (void *)(8UL + (ulong)_pq), max_writers_per_block );
     221        4032 :   }
     222        1008 :   return mem;
     223        1008 : }
     224             : 
     225             : static inline fd_rdisp_mq_t *
     226        1008 : fd_rdisp_mq_join( void * mem ) {
     227        1008 :   fd_rdisp_mq_t * mq                    = (fd_rdisp_mq_t *)mem;
     228        1008 :   ulong           acct_depth            = mq->acct_depth;
     229        1008 :   ulong           max_writers_per_block = mq->max_writers_per_block;
     230             : 
     231        1008 :   FD_SCRATCH_ALLOC_INIT( l, mem );
     232        1008 :   /*                   */ FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_rdisp_mq_t),    sizeof(fd_rdisp_mq_t)                     );
     233        1008 :   fd_rdisp_mq_4u_t * u4 = FD_SCRATCH_ALLOC_APPEND( l, alignof(fd_rdisp_mq_4u_t), (acct_depth+1UL)*sizeof(fd_rdisp_mq_4u_t) );
     234             : 
     235           0 :   mq->flat = u4;
     236        5040 :   for( ulong i=0UL; i<4UL; i++ ) {
     237        4032 :     void * _pq = FD_SCRATCH_ALLOC_APPEND( l, 8UL, sizeof(void *)+fd_rdisp_mprq_footprint( max_writers_per_block ) );
     238           0 :     *((uint **)_pq) = u4->per_lane+i;
     239        4032 :     mq->prq[i] = fd_rdisp_mprq_join( (void *)(sizeof(void *) + (ulong)_pq) );
     240        4032 :   }
     241        1008 :   return mq;
     242        1008 : }
     243             : 
     244             : static inline int
     245             : fd_rdisp_mq_test( fd_rdisp_mq_t const * mq,
     246             :                   ulong                 acct_idx,
     247        6226 :                   ulong                 staging_lane ) {
     248        6226 :   return mq->flat[acct_idx].per_lane[staging_lane]!=UINT_MAX;
     249        6226 : }
     250             : 
     251             : static inline int
     252             : fd_rdisp_mq_test_prio0( fd_rdisp_mq_t const * mq,
     253             :                         ulong                 acct_idx,
     254         224 :                         ulong                 staging_lane ) {
     255         224 :   return mq->flat[acct_idx].per_lane[staging_lane]>fd_rdisp_mprq_cnt( mq->prq[staging_lane] );
     256         224 : }
     257             : 
     258             : static inline ulong
     259             : fd_rdisp_mq_prio0_cnt( fd_rdisp_mq_t const * mq,
     260           0 :                        ulong                 staging_lane ) {
     261           0 :   return mq->prio0_cnt[staging_lane];
     262           0 : }
     263             : 
     264             : static inline void
     265             : fd_rdisp_mq_insert( fd_rdisp_mq_t * mq,
     266             :                     ulong           acct_idx,
     267             :                     ulong           staging_lane,
     268        2888 :                     float           prio ) {
     269        2888 :   fd_rdisp_mq_prqe_t temp[1] = {{
     270        2888 :     .idx  = (uint)acct_idx,
     271        2888 :     .prio = prio,
     272        2888 :   }};
     273        2888 :   if( FD_UNLIKELY( prio==0.0f ) ) {
     274           0 :     ulong fidx = mq->max_writers_per_block - (mq->prio0_cnt[staging_lane]++);
     275           0 :     mq->prq[staging_lane][fidx] = *temp;
     276           0 :     mq->flat[acct_idx].per_lane[staging_lane] = (uint)fidx;
     277        2888 :   } else {
     278        2888 :     fd_rdisp_mprq_insert( mq->prq[staging_lane], temp );
     279             :     /* flat is auto-populated by _insert */
     280        2888 :   }
     281        2888 : }
     282             : 
     283             : static inline void
     284             : fd_rdisp_mq_adjust( fd_rdisp_mq_t * mq,
     285             :                     ulong           acct_idx,
     286             :                     ulong           staging_lane,
     287           0 :                     float           prio ) {
     288           0 :   ulong M      = mq->max_writers_per_block;
     289           0 :   ulong p0_cnt = mq->prio0_cnt[staging_lane];
     290             : 
     291           0 :   fd_rdisp_mq_prqe_t * prq = mq->prq[staging_lane];
     292             : 
     293           0 :   uint fidx                = mq->flat[acct_idx].per_lane[staging_lane];
     294           0 :   int  pre_prio0           = fd_rdisp_mq_test_prio0( mq, acct_idx, staging_lane );
     295           0 :   int  new_prio0           = prio==0.0f;
     296             : 
     297           0 :   if( FD_UNLIKELY( pre_prio0 & new_prio0 ) ) return; /* nothing to do */
     298           0 :   fd_rdisp_mq_prqe_t temp = prq[fidx];
     299             : 
     300           0 :   if( pre_prio0 ) {
     301             :     /* move tail to hole (they could be the same) */
     302           0 :     prq[fidx] = prq[M - (--p0_cnt)];
     303           0 :     mq->flat[prq[fidx].idx].per_lane[staging_lane] = fidx;
     304           0 :   } else {
     305           0 :     fd_rdisp_mprq_remove( prq, fidx );
     306           0 :   }
     307             : 
     308           0 :   temp.prio = prio;
     309             : 
     310           0 :   if( new_prio0 ) {
     311           0 :     ulong new_idx = M-(p0_cnt++);
     312           0 :     prq[new_idx] = temp;
     313           0 :     mq->flat[acct_idx].per_lane[staging_lane] = (uint)new_idx;
     314           0 :   } else {
     315           0 :     fd_rdisp_mprq_insert( prq, &temp );
     316           0 :   }
     317             : 
     318           0 :   mq->prio0_cnt[staging_lane] = p0_cnt;
     319           0 : }
     320             : 
     321             : static inline ulong
     322             : fd_rdisp_mq_pop( fd_rdisp_mq_t * mq,
     323           0 :                  ulong           staging_lane ) {
     324           0 :   fd_rdisp_mq_prqe_t * prq = mq->prq[staging_lane];
     325             : 
     326           0 :   if( FD_LIKELY( mq->prio0_cnt[ staging_lane ] ) ) {
     327           0 :     ulong tail_idx  = mq->max_writers_per_block - (--mq->prio0_cnt[staging_lane]);
     328           0 :     ulong to_return = prq[tail_idx].idx;
     329           0 :     mq->flat[to_return].per_lane[staging_lane] = UINT_MAX;
     330           0 :     return to_return;
     331           0 :   } else {
     332           0 :     ulong to_return = prq->idx;
     333           0 :     fd_rdisp_mprq_remove_min( prq );
     334           0 :     mq->flat[to_return].per_lane[staging_lane] = UINT_MAX;
     335           0 :     return to_return;
     336           0 :   }
     337           0 : }
     338             : 
     339             : static inline ulong
     340             : fd_rdisp_mq_cnt( fd_rdisp_mq_t * mq,
     341        2891 :                  ulong           staging_lane ) {
     342        2891 :   return fd_rdisp_mprq_cnt( mq->prq[staging_lane] ) + mq->prio0_cnt[staging_lane];
     343        2891 : }
     344             : 
     345           0 : static inline void * fd_rdisp_mq_leave ( fd_rdisp_mq_t * mq ) { return (void *)mq;  }
     346           0 : static inline void * fd_rdisp_mq_delete( void * mem )         { return (void *)mem; }
     347             : 
     348             : FD_PROTOTYPES_END
     349             : 
     350             : #endif /* HEADER_fd_src_discof_replay_fd_rdisp_mq_h */

Generated by: LCOV version 1.14