22//!
33//! It is used to optimize access to precompile addresses.
44
5- use bitvec:: { bitvec, vec:: BitVec } ;
65use context_interface:: journaled_state:: JournalLoadError ;
76use primitives:: {
87 short_address, Address , AddressMap , AddressSet , HashSet , StorageKey , SHORT_ADDRESS_CAP ,
98} ;
109
10+ /// Number of bytes needed to hold SHORT_ADDRESS_CAP bits (300 bits == 38 bytes).
11+ const PRECOMPILE_SHORT_ADDRESS_BYTES : usize = ( SHORT_ADDRESS_CAP + 7 ) / 8 ;
12+
13+ #[ cfg( feature = "serde" ) ]
14+ mod fixed_array {
15+ use serde:: { Deserialize , Deserializer , Serializer } ;
16+
17+ pub ( super ) fn serialize < S : Serializer , const N : usize > (
18+ arr : & [ u8 ; N ] ,
19+ serializer : S ,
20+ ) -> Result < S :: Ok , S :: Error > {
21+ serializer. serialize_bytes ( arr)
22+ }
23+
24+ pub ( super ) fn deserialize < ' de , D : Deserializer < ' de > , const N : usize > (
25+ deserializer : D ,
26+ ) -> Result < [ u8 ; N ] , D :: Error > {
27+ let bytes: & [ u8 ] = Deserialize :: deserialize ( deserializer) ?;
28+ if bytes. len ( ) != N {
29+ return Err ( serde:: de:: Error :: custom ( std:: format!(
30+ "expected {} bytes, got {}" ,
31+ N ,
32+ bytes. len( )
33+ ) ) ) ;
34+ }
35+ let mut arr = [ 0u8 ; N ] ;
36+ arr. copy_from_slice ( bytes) ;
37+ Ok ( arr)
38+ }
39+ }
40+
1141/// Stores addresses that are warm loaded. Contains precompiles and coinbase address.
1242///
1343/// It contains precompiles addresses that are not changed frequently and AccessList that
@@ -22,9 +52,11 @@ use primitives::{
2252pub struct WarmAddresses {
2353 /// Set of warm loaded precompile addresses.
2454 precompile_set : AddressSet ,
25- /// Bit vector of precompile short addresses. If address is shorter than [`SHORT_ADDRESS_CAP`] it
26- /// will be stored in this bit vector for faster access.
27- precompile_short_addresses : BitVec ,
55+ /// Fixed bitset of precompile short addresses (one bit per possible short address).
56+ /// Uses a plain byte array + manual bit ops for fast access in the hot path
57+ /// (avoids BitVec overhead, similar to JumpTable optimization).
58+ #[ cfg_attr( feature = "serde" , serde( with = "fixed_array" ) ) ]
59+ precompile_short_addresses : [ u8 ; PRECOMPILE_SHORT_ADDRESS_BYTES ] ,
2860 /// `true` if all precompiles are short addresses.
2961 precompile_all_short_addresses : bool ,
3062 /// Coinbase address.
@@ -45,7 +77,7 @@ impl WarmAddresses {
4577 pub fn new ( ) -> Self {
4678 Self {
4779 precompile_set : AddressSet :: default ( ) ,
48- precompile_short_addresses : bitvec ! [ 0 ; SHORT_ADDRESS_CAP ] ,
80+ precompile_short_addresses : [ 0u8 ; PRECOMPILE_SHORT_ADDRESS_BYTES ] ,
4981 precompile_all_short_addresses : true ,
5082 coinbase : None ,
5183 access_list : AddressMap :: default ( ) ,
@@ -66,12 +98,12 @@ impl WarmAddresses {
6698
6799 /// Set the precompile addresses and short addresses.
68100 pub fn set_precompile_addresses ( & mut self , addresses : & AddressSet ) {
69- self . precompile_short_addresses . fill ( false ) ;
101+ self . precompile_short_addresses . fill ( 0 ) ;
70102
71103 let mut all_short_addresses = true ;
72104 for address in addresses. iter ( ) {
73105 if let Some ( short_address) = short_address ( address) {
74- self . precompile_short_addresses . set ( short_address, true ) ;
106+ self . set_precompile_short ( short_address) ;
75107 } else {
76108 all_short_addresses = false ;
77109 }
@@ -81,6 +113,31 @@ impl WarmAddresses {
81113 self . precompile_set . clone_from ( addresses) ;
82114 }
83115
116+ /// Set the bit for a short precompile address.
117+ ///
118+ /// Uses manual bit manipulation on a fixed byte array (instead of `BitVec`)
119+ /// to avoid per-access overhead
120+ #[ inline( always) ]
121+ fn set_precompile_short ( & mut self , idx : usize ) {
122+ // get the index to the byte in the arr
123+ let byte = idx >> 3 ;
124+ // bit position within the byte (0..7)
125+ let bit = 1 << ( idx & 7 ) ;
126+ self . precompile_short_addresses [ byte] |= bit;
127+ }
128+
129+ /// Returns whether the bit for the given short address index is set.
130+ ///
131+ /// This is the hot path inside `is_warm`/`is_cold` for short precompile
132+ /// addresses. We perform direct byte + bit operations on the fixed array
133+ /// instead of going through `BitVec` (same technique I used in `JumpTable::is_valid`).
134+ #[ inline( always) ]
135+ pub ( crate ) fn is_precompile_short ( & self , idx : usize ) -> bool {
136+ let byte = idx >> 3 ;
137+ let bit = 1 << ( idx & 7 ) ;
138+ self . precompile_short_addresses [ byte] & bit != 0
139+ }
140+
84141 /// Set the coinbase address.
85142 #[ inline]
86143 pub const fn set_coinbase ( & mut self , address : Address ) {
@@ -131,7 +188,7 @@ impl WarmAddresses {
131188
132189 // check if it is short precompile address
133190 if let Some ( short_address) = short_address ( address) {
134- return self . precompile_short_addresses [ short_address] ;
191+ return self . is_precompile_short ( short_address) ;
135192 }
136193
137194 if !self . precompile_all_short_addresses {
@@ -185,9 +242,12 @@ mod tests {
185242 assert ! ( warm_addresses. precompile_set. is_empty( ) ) ;
186243 assert_eq ! (
187244 warm_addresses. precompile_short_addresses. len( ) ,
188- SHORT_ADDRESS_CAP
245+ PRECOMPILE_SHORT_ADDRESS_BYTES
189246 ) ;
190- assert ! ( !warm_addresses. precompile_short_addresses. any( ) ) ;
247+ assert ! ( warm_addresses
248+ . precompile_short_addresses
249+ . iter( )
250+ . all( |& b| b == 0 ) ) ;
191251 assert ! ( warm_addresses. coinbase. is_none( ) ) ;
192252
193253 // Test Default trait
@@ -234,13 +294,13 @@ mod tests {
234294 assert_eq ! ( warm_addresses. precompile_set, precompiles) ;
235295 assert_eq ! (
236296 warm_addresses. precompile_short_addresses. len( ) ,
237- SHORT_ADDRESS_CAP
297+ PRECOMPILE_SHORT_ADDRESS_BYTES
238298 ) ;
239299
240- // Verify bitvec optimization
241- assert ! ( warm_addresses. precompile_short_addresses [ 1 ] ) ;
242- assert ! ( warm_addresses. precompile_short_addresses [ 5 ] ) ;
243- assert ! ( !warm_addresses. precompile_short_addresses [ 0 ] ) ;
300+ // Verify optimization (replaces old BitVec)
301+ assert ! ( warm_addresses. is_precompile_short ( 1 ) ) ;
302+ assert ! ( warm_addresses. is_precompile_short ( 5 ) ) ;
303+ assert ! ( !warm_addresses. is_precompile_short ( 0 ) ) ;
244304
245305 // Verify warmth detection
246306 assert ! ( warm_addresses. is_warm( & short_addr1) ) ;
@@ -272,7 +332,10 @@ mod tests {
272332
273333 // Verify storage
274334 assert_eq ! ( warm_addresses. precompile_set, precompiles) ;
275- assert ! ( !warm_addresses. precompile_short_addresses. any( ) ) ;
335+ assert ! ( warm_addresses
336+ . precompile_short_addresses
337+ . iter( )
338+ . all( |& b| b == 0 ) ) ;
276339
277340 // Verify warmth detection
278341 assert ! ( warm_addresses. is_warm( & regular_addr) ) ;
@@ -303,8 +366,8 @@ mod tests {
303366 assert ! ( warm_addresses. is_warm( & regular_addr) ) ;
304367
305368 // Verify short address optimization is used
306- assert ! ( warm_addresses. precompile_short_addresses [ 7 ] ) ;
307- assert ! ( !warm_addresses. precompile_short_addresses [ 8 ] ) ;
369+ assert ! ( warm_addresses. is_precompile_short ( 7 ) ) ;
370+ assert ! ( !warm_addresses. is_precompile_short ( 8 ) ) ;
308371 }
309372
310373 #[ test]
@@ -324,6 +387,174 @@ mod tests {
324387 warm_addresses. set_precompile_addresses ( & precompiles) ;
325388
326389 assert ! ( warm_addresses. is_warm( & boundary_addr) ) ;
327- assert ! ( warm_addresses. precompile_short_addresses[ SHORT_ADDRESS_CAP - 1 ] ) ;
390+ assert ! ( warm_addresses. is_precompile_short( SHORT_ADDRESS_CAP - 1 ) ) ;
391+ }
392+ }
393+
394+ #[ cfg( test) ]
395+ mod bench_is_short_precompile {
396+ use super :: * ;
397+ use std:: time:: Instant ;
398+
399+ use bitvec:: { bitvec, vec:: BitVec } ;
400+
401+ const ITERATIONS : usize = 1_000_000 ;
402+ const TEST_SIZE : usize = SHORT_ADDRESS_CAP ;
403+
404+ /// Legacy BitVec version (for comparison only).
405+ #[ derive( Clone ) ]
406+ struct ShortAddressesWithBitVec {
407+ bits : BitVec ,
408+ }
409+
410+ impl ShortAddressesWithBitVec {
411+ fn new ( ) -> Self {
412+ Self {
413+ bits : bitvec ! [ 0 ; SHORT_ADDRESS_CAP ] ,
414+ }
415+ }
416+
417+ fn set_many ( & mut self , indices : impl IntoIterator < Item = usize > ) {
418+ for i in indices {
419+ if i < SHORT_ADDRESS_CAP {
420+ self . bits . set ( i, true ) ;
421+ }
422+ }
423+ }
424+
425+ /// Old BitVec [] style (for comparison).
426+ #[ inline]
427+ fn is_set ( & self , idx : usize ) -> bool {
428+ self . bits [ idx]
429+ }
430+ }
431+
432+ fn create_test_data ( ) -> ( WarmAddresses , ShortAddressesWithBitVec ) {
433+ let mut real = WarmAddresses :: new ( ) ;
434+ let mut precompile_set = AddressSet :: default ( ) ;
435+
436+ for i in ( 0 ..TEST_SIZE ) . step_by ( 3 ) {
437+ let mut bytes = [ 0u8 ; 20 ] ;
438+ bytes[ 18 ] = ( i >> 8 ) as u8 ;
439+ bytes[ 19 ] = i as u8 ;
440+ precompile_set. insert ( Address :: from ( bytes) ) ;
441+ }
442+ real. set_precompile_addresses ( & precompile_set) ;
443+
444+ let mut legacy = ShortAddressesWithBitVec :: new ( ) ;
445+ legacy. set_many ( ( 0 ..TEST_SIZE ) . step_by ( 3 ) ) ;
446+
447+ ( real, legacy)
448+ }
449+
450+ fn benchmark_implementation < F > ( name : & str , table : & F , test_fn : impl Fn ( & F , usize ) -> bool )
451+ where
452+ F : Clone ,
453+ {
454+ for i in 0 ..10_000 {
455+ std:: hint:: black_box ( test_fn ( table, i % TEST_SIZE ) ) ;
456+ }
457+
458+ let start = Instant :: now ( ) ;
459+ let mut count = 0 ;
460+
461+ for i in 0 ..ITERATIONS {
462+ if test_fn ( table, i % TEST_SIZE ) {
463+ count += 1 ;
464+ }
465+ }
466+
467+ let duration = start. elapsed ( ) ;
468+ let ns_per_op = duration. as_nanos ( ) as f64 / ITERATIONS as f64 ;
469+ let ops_per_sec = ITERATIONS as f64 / duration. as_secs_f64 ( ) ;
470+
471+ println ! ( "{name} Performance:" ) ;
472+ println ! ( " Time per op: {ns_per_op:.2} ns" ) ;
473+ println ! ( " Ops per sec: {ops_per_sec:.0}" ) ;
474+ println ! ( " True count: {count}" ) ;
475+ println ! ( ) ;
476+
477+ std:: hint:: black_box ( count) ;
478+ }
479+
480+ #[ test]
481+ fn bench_is_short_precompile ( ) {
482+ println ! ( "\n WarmAddresses short precompile bit test Benchmark Comparison" ) ;
483+ println ! ( "============================================================" ) ;
484+
485+ let ( real_warm, legacy_bitvec) = create_test_data ( ) ;
486+
487+ benchmark_implementation (
488+ "WarmAddresses (Fixed Array + Manual Bits)" ,
489+ & real_warm,
490+ |wa, idx| wa. is_precompile_short ( idx) ,
491+ ) ;
492+
493+ benchmark_implementation ( "Legacy (BitVec indexing)" , & legacy_bitvec, |lb, idx| {
494+ lb. is_set ( idx)
495+ } ) ;
496+
497+ println ! ( "Benchmark completed successfully!\n " ) ;
498+ }
499+
500+ #[ test]
501+ fn bench_different_access_patterns ( ) {
502+ let ( real_warm, legacy_bitvec) = create_test_data ( ) ;
503+
504+ println ! ( "Short Precompile Access Pattern Comparison" ) ;
505+ println ! ( "==========================================" ) ;
506+
507+ let start = Instant :: now ( ) ;
508+ for i in 0 ..ITERATIONS {
509+ std:: hint:: black_box ( real_warm. is_precompile_short ( i % TEST_SIZE ) ) ;
510+ }
511+ let fixed_sequential = start. elapsed ( ) ;
512+
513+ let start = Instant :: now ( ) ;
514+ for i in 0 ..ITERATIONS {
515+ std:: hint:: black_box ( legacy_bitvec. is_set ( i % TEST_SIZE ) ) ;
516+ }
517+ let bitvec_sequential = start. elapsed ( ) ;
518+
519+ let start = Instant :: now ( ) ;
520+ for i in 0 ..ITERATIONS {
521+ std:: hint:: black_box ( real_warm. is_precompile_short ( ( i * 17 ) % TEST_SIZE ) ) ;
522+ }
523+ let fixed_random = start. elapsed ( ) ;
524+
525+ let start = Instant :: now ( ) ;
526+ for i in 0 ..ITERATIONS {
527+ std:: hint:: black_box ( legacy_bitvec. is_set ( ( i * 17 ) % TEST_SIZE ) ) ;
528+ }
529+ let bitvec_random = start. elapsed ( ) ;
530+
531+ println ! ( "Sequential Access:" ) ;
532+ println ! (
533+ " Fixed Array: {:.2} ns/op" ,
534+ fixed_sequential. as_nanos( ) as f64 / ITERATIONS as f64
535+ ) ;
536+ println ! (
537+ " BitVec: {:.2} ns/op" ,
538+ bitvec_sequential. as_nanos( ) as f64 / ITERATIONS as f64
539+ ) ;
540+ println ! (
541+ " Speedup: {:.1}x" ,
542+ bitvec_sequential. as_nanos( ) as f64 / fixed_sequential. as_nanos( ) as f64
543+ ) ;
544+
545+ println ! ( ) ;
546+ println ! ( "Random Access:" ) ;
547+ println ! (
548+ " Fixed Array: {:.2} ns/op" ,
549+ fixed_random. as_nanos( ) as f64 / ITERATIONS as f64
550+ ) ;
551+ println ! (
552+ " BitVec: {:.2} ns/op" ,
553+ bitvec_random. as_nanos( ) as f64 / ITERATIONS as f64
554+ ) ;
555+ println ! (
556+ " Speedup: {:.1}x" ,
557+ bitvec_random. as_nanos( ) as f64 / fixed_random. as_nanos( ) as f64
558+ ) ;
328559 }
329560}
0 commit comments