1
1
use crate :: fx:: { FxHashMap , FxHasher } ;
2
2
#[ cfg( parallel_compiler) ]
3
- use crate :: sync:: is_dyn_thread_safe;
4
- use crate :: sync:: { CacheAligned , Lock , LockGuard } ;
3
+ use crate :: sync:: { is_dyn_thread_safe, CacheAligned } ;
4
+ use crate :: sync:: { Lock , LockGuard } ;
5
5
use std:: borrow:: Borrow ;
6
6
use std:: collections:: hash_map:: RawEntryMut ;
7
7
use std:: hash:: { Hash , Hasher } ;
8
8
use std:: mem;
9
9
10
- #[ cfg( parallel_compiler) ]
11
10
// 32 shards is sufficient to reduce contention on an 8-core Ryzen 7 1700,
12
11
// but this should be tested on higher core count CPUs. How the `Sharded` type gets used
13
12
// may also affect the ideal number of shards.
14
- const SHARD_BITS : usize = 5 ;
15
-
16
- #[ cfg( not( parallel_compiler) ) ]
17
- const SHARD_BITS : usize = 0 ;
13
+ const SHARD_BITS : usize = if cfg ! ( parallel_compiler) { 5 } else { 0 } ;
18
14
19
15
pub const SHARDS : usize = 1 << SHARD_BITS ;
20
16
21
17
/// An array of cache-line aligned inner locked structures with convenience methods.
22
- pub struct Sharded < T > {
23
- /// This mask is used to ensure that accesses are inbounds of `shards`.
24
- /// When dynamic thread safety is off, this field is set to 0 causing only
25
- /// a single shard to be used for greater cache efficiency.
18
+ /// A single field is used when the compiler uses only one thread.
19
+ pub enum Sharded < T > {
20
+ Single ( Lock < T > ) ,
26
21
#[ cfg( parallel_compiler) ]
27
- mask : usize ,
28
- shards : [ CacheAligned < Lock < T > > ; SHARDS ] ,
22
+ Shards ( Box < [ CacheAligned < Lock < T > > ; SHARDS ] > ) ,
29
23
}
30
24
31
25
impl < T : Default > Default for Sharded < T > {
@@ -38,29 +32,14 @@ impl<T: Default> Default for Sharded<T> {
38
32
impl < T > Sharded < T > {
39
33
#[ inline]
40
34
pub fn new ( mut value : impl FnMut ( ) -> T ) -> Self {
41
- Sharded {
42
- #[ cfg( parallel_compiler) ]
43
- mask : if is_dyn_thread_safe ( ) { SHARDS - 1 } else { 0 } ,
44
- shards : [ ( ) ; SHARDS ] . map ( |( ) | CacheAligned ( Lock :: new ( value ( ) ) ) ) ,
45
- }
46
- }
47
-
48
- #[ inline( always) ]
49
- fn mask ( & self ) -> usize {
50
35
#[ cfg( parallel_compiler) ]
51
- {
52
- if SHARDS == 1 { 0 } else { self . mask }
53
- }
54
- #[ cfg( not( parallel_compiler) ) ]
55
- {
56
- 0
36
+ if is_dyn_thread_safe ( ) {
37
+ return Sharded :: Shards ( Box :: new (
38
+ [ ( ) ; SHARDS ] . map ( |( ) | CacheAligned ( Lock :: new ( value ( ) ) ) ) ,
39
+ ) ) ;
57
40
}
58
- }
59
41
60
- #[ inline( always) ]
61
- fn count ( & self ) -> usize {
62
- // `self.mask` is always one below the used shard count
63
- self . mask ( ) + 1
42
+ Sharded :: Single ( Lock :: new ( value ( ) ) )
64
43
}
65
44
66
45
/// The shard is selected by hashing `val` with `FxHasher`.
@@ -75,9 +54,24 @@ impl<T> Sharded<T> {
75
54
}
76
55
77
56
#[ inline]
78
- pub fn get_shard_by_index ( & self , i : usize ) -> & Lock < T > {
79
- // SAFETY: The index get ANDed with the mask, ensuring it is always inbounds.
80
- unsafe { & self . shards . get_unchecked ( i & self . mask ( ) ) . 0 }
57
+ pub fn get_shard_by_index ( & self , _i : usize ) -> & Lock < T > {
58
+ match self {
59
+ Self :: Single ( single) => & single,
60
+ #[ cfg( parallel_compiler) ]
61
+ Self :: Shards ( shards) => {
62
+ // SAFETY: The index gets ANDed with the shard mask, ensuring it is always inbounds.
63
+ unsafe { & shards. get_unchecked ( _i & ( SHARDS - 1 ) ) . 0 }
64
+ }
65
+ }
66
+ }
67
+
68
+ #[ inline]
69
+ fn count ( & self ) -> usize {
70
+ match self {
71
+ Self :: Single ( ..) => 1 ,
72
+ #[ cfg( parallel_compiler) ]
73
+ Self :: Shards ( ..) => SHARDS ,
74
+ }
81
75
}
82
76
83
77
pub fn lock_shards ( & self ) -> Vec < LockGuard < ' _ , T > > {
0 commit comments