Line data Source code
1 : #ifndef HEADER_fd_src_disco_pack_fd_pack_pacing_h 2 : #define HEADER_fd_src_disco_pack_fd_pack_pacing_h 3 : 4 : #include "../../util/bits/fd_bits.h" 5 : #include <math.h> 6 : 7 : /* One of the keys to packing well is properly pacing CU consumption. 8 : Without pacing, pack will end up filling the block with non-ideal 9 : transactions. Since at the current limits, the banks can execute a 10 : block worth of CUs in a fraction of the block time, without pacing, 11 : any lucrative transactions that arrive at towards the end of a block 12 : will have to be delayed until the next block (or another leader's 13 : block if it's the last in our rotation). */ 14 : 15 : 16 : struct fd_pack_pacing_private { 17 : /* Start and end time of block in ticks */ 18 : long t_start; 19 : long t_end; 20 : /* Number of CUs in the block */ 21 : float max_cus; 22 : 23 : float ticks_per_cu; 24 : float remaining_cus; 25 : }; 26 : 27 : typedef struct fd_pack_pacing_private fd_pack_pacing_t; 28 : 29 : 30 : /* fd_pack_pacing_init begins pacing for a slot which starts at now and 31 : ends at t_end (both measured in fd_tickcount() space) and will 32 : contain cus CUs. cus in (0, 2^32). t_end - t_start should be about 33 : 400ms or less, but must be in (0, 2^32) as well. */ 34 : static inline void 35 : fd_pack_pacing_init( fd_pack_pacing_t * pacer, 36 : long t_start, 37 : long t_end, 38 : float ticks_per_ns, 39 34 : ulong max_cus ) { 40 : 41 34 : pacer->t_start = t_start; 42 34 : pacer->t_end = t_end; 43 : 44 : /* Time per CU depends on the hardware, the transaction mix, what 45 : fraction of the transactions land, etc. It's hard to just come up 46 : with a value, but a small sample says 9 ns/CU is in the right 47 : ballpark. */ 48 34 : pacer->ticks_per_cu = 9.0f * ticks_per_ns; 49 : 50 : /* Originally, we had all the lines ending 5% before t_end, but the 51 : better thing to do is to adjust max_cus up so that the 1 bank line 52 : ends 5% before t_end. */ 53 34 : pacer->max_cus = (float)max_cus + 0.05f * (float)(t_end-t_start)/pacer->ticks_per_cu; 54 34 : pacer->remaining_cus = pacer->max_cus; 55 34 : } 56 : 57 : /* fd_pack_pacing_update_consumed_cus notes that the instantaneous value 58 : of consumed CUs may have updated. pacer must be a local join. 59 : consumed_cus should be below the value of max_cus but it's treated as 60 : max_cus if it's larger. Now should be the time (in fd_tickcount 61 : space) at which the measurement was taken. */ 62 : static inline void 63 : fd_pack_pacing_update_consumed_cus( fd_pack_pacing_t * pacer, 64 : ulong consumed_cus, 65 1368 : long now ) { 66 : /* Keep this function separate so in the future we can learn the 67 : ticks_per_cu rate. */ 68 1368 : (void)now; 69 : /* It's possible (but unlikely) that consumed_cus can be greater than 70 : max_cus, so clamp the value at 0 */ 71 1368 : pacer->remaining_cus = fmaxf( pacer->max_cus-(float)consumed_cus, 0.0f ); 72 1368 : } 73 : 74 : 75 : /* fd_pack_pacing_enabled_bank_cnt computes how many banks should be 76 : active at time `now` (in fd_tickcount space) given the most recent 77 : value specified for consumed CUs. The returned value may be 0, which 78 : indicates that no banks should be active at the moment. It may also 79 : be higher than the number of available banks, which should be 80 : interpreted as all banks being enabled. */ 81 : FD_FN_PURE static inline ulong 82 : fd_pack_pacing_enabled_bank_cnt( fd_pack_pacing_t const * pacer, 83 4669 : long now ) { 84 : /* We want to use as few banks as possible to fill the block in 400 85 : milliseconds. That way we pass up the best transaction because it 86 : conflicts with something actively running as infrequently as 87 : possible. To do that, we draw lines through in the time-CU plane 88 : that pass through approximately (400 milliseconds, 48M CUs) with 89 : slope k*(single bank speed), where k varies between 1 and the 90 : number of bank tiles configured. This splits the plane into 91 : several regions, and the region we are in tells us how many bank 92 : tiles to use. 93 : 94 : 95 : 48M - / /| 96 : | / / / 97 : | / // | 98 : U | / / / / 99 : s | 0 banks active / / / | 100 : e | / / / / 101 : d | / e / / | 102 : | / k v / / / 103 : C | / n i / / | 104 : U | / a t / / / 105 : s | / B c / / | 106 : | / 1 a / 2 Banks / / 107 : | / / active / ... | 108 : 0 |-------------------------------------------------------- 109 : 0 ms 400ms 110 : */ 111 : /* We want to be pretty careful with the math here. We want to make 112 : sure we never divide by 0, so clamp the denominator at 1. The 113 : numerator is non-negative. Ticks_per_cu is between 1 and 100, so 114 : it'll always fit in a ulong. */ 115 4669 : return (ulong)(pacer->remaining_cus/ 116 4669 : (float)(fd_long_max( 1L, pacer->t_end - now )) * pacer->ticks_per_cu ); 117 4669 : } 118 : 119 : #endif /* HEADER_fd_src_disco_pack_fd_pack_pacing_h */