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