Skip to content

Commit 3fe8285

Browse files
committed
Auto merge of #61779 - Zoxc:sharded, r=oli-obk
Use sharded maps for interning Cuts down runtime from 5.5s to 3.8s for non-incremental `syntex_syntax` check builds with 16 threads / 8 cores. r? @eddyb
2 parents 95b1fe5 + 0e73386 commit 3fe8285

File tree

4 files changed

+149
-77
lines changed

4 files changed

+149
-77
lines changed

src/librustc/ty/context.rs

+20-18
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,14 @@ use crate::util::common::ErrorReported;
4646
use crate::util::nodemap::{DefIdMap, DefIdSet, ItemLocalMap, ItemLocalSet};
4747
use crate::util::nodemap::{FxHashMap, FxHashSet};
4848
use errors::DiagnosticBuilder;
49-
use rustc_data_structures::interner::HashInterner;
5049
use smallvec::SmallVec;
5150
use rustc_data_structures::stable_hasher::{HashStable, hash_stable_hashmap,
5251
StableHasher, StableHasherResult,
5352
StableVec};
5453
use arena::SyncDroplessArena;
5554
use rustc_data_structures::indexed_vec::{Idx, IndexVec};
5655
use rustc_data_structures::sync::{Lrc, Lock, WorkerLocal};
56+
use rustc_data_structures::sharded::ShardedHashMap;
5757
use std::any::Any;
5858
use std::borrow::Borrow;
5959
use std::cmp::Ordering;
@@ -88,7 +88,7 @@ impl AllArenas {
8888
}
8989
}
9090

91-
type InternedSet<'tcx, T> = Lock<FxHashMap<Interned<'tcx, T>, ()>>;
91+
type InternedSet<'tcx, T> = ShardedHashMap<Interned<'tcx, T>, ()>;
9292

9393
pub struct CtxtInterners<'tcx> {
9494
/// The arena that types, regions, etc are allocated from
@@ -135,7 +135,7 @@ impl<'tcx> CtxtInterners<'tcx> {
135135
fn intern_ty(&self,
136136
st: TyKind<'tcx>
137137
) -> Ty<'tcx> {
138-
self.type_.borrow_mut().intern(st, |st| {
138+
self.type_.intern(st, |st| {
139139
let flags = super::flags::FlagComputation::for_sty(&st);
140140

141141
let ty_struct = TyS {
@@ -924,7 +924,7 @@ impl<'tcx> CommonTypes<'tcx> {
924924
impl<'tcx> CommonLifetimes<'tcx> {
925925
fn new(interners: &CtxtInterners<'tcx>) -> CommonLifetimes<'tcx> {
926926
let mk = |r| {
927-
interners.region.borrow_mut().intern(r, |r| {
927+
interners.region.intern(r, |r| {
928928
Interned(interners.arena.alloc(r))
929929
}).0
930930
};
@@ -940,7 +940,7 @@ impl<'tcx> CommonLifetimes<'tcx> {
940940
impl<'tcx> CommonConsts<'tcx> {
941941
fn new(interners: &CtxtInterners<'tcx>, types: &CommonTypes<'tcx>) -> CommonConsts<'tcx> {
942942
let mk_const = |c| {
943-
interners.const_.borrow_mut().intern(c, |c| {
943+
interners.const_.intern(c, |c| {
944944
Interned(interners.arena.alloc(c))
945945
}).0
946946
};
@@ -1053,14 +1053,14 @@ pub struct GlobalCtxt<'tcx> {
10531053
/// Data layout specification for the current target.
10541054
pub data_layout: TargetDataLayout,
10551055

1056-
stability_interner: Lock<FxHashMap<&'tcx attr::Stability, ()>>,
1056+
stability_interner: ShardedHashMap<&'tcx attr::Stability, ()>,
10571057

10581058
/// Stores the value of constants (and deduplicates the actual memory)
1059-
allocation_interner: Lock<FxHashMap<&'tcx Allocation, ()>>,
1059+
allocation_interner: ShardedHashMap<&'tcx Allocation, ()>,
10601060

10611061
pub alloc_map: Lock<interpret::AllocMap<'tcx>>,
10621062

1063-
layout_interner: Lock<FxHashMap<&'tcx LayoutDetails, ()>>,
1063+
layout_interner: ShardedHashMap<&'tcx LayoutDetails, ()>,
10641064

10651065
/// A general purpose channel to throw data out the back towards LLVM worker
10661066
/// threads.
@@ -1103,7 +1103,7 @@ impl<'tcx> TyCtxt<'tcx> {
11031103
}
11041104

11051105
pub fn intern_const_alloc(self, alloc: Allocation) -> &'tcx Allocation {
1106-
self.allocation_interner.borrow_mut().intern(alloc, |alloc| {
1106+
self.allocation_interner.intern(alloc, |alloc| {
11071107
self.arena.alloc(alloc)
11081108
})
11091109
}
@@ -1117,13 +1117,13 @@ impl<'tcx> TyCtxt<'tcx> {
11171117
}
11181118

11191119
pub fn intern_stability(self, stab: attr::Stability) -> &'tcx attr::Stability {
1120-
self.stability_interner.borrow_mut().intern(stab, |stab| {
1120+
self.stability_interner.intern(stab, |stab| {
11211121
self.arena.alloc(stab)
11221122
})
11231123
}
11241124

11251125
pub fn intern_layout(self, layout: LayoutDetails) -> &'tcx LayoutDetails {
1126-
self.layout_interner.borrow_mut().intern(layout, |layout| {
1126+
self.layout_interner.intern(layout, |layout| {
11271127
self.arena.alloc(layout)
11281128
})
11291129
}
@@ -2023,7 +2023,9 @@ macro_rules! sty_debug_print {
20232023
};
20242024
$(let mut $variant = total;)*
20252025

2026-
for &Interned(t) in tcx.interners.type_.borrow().keys() {
2026+
let shards = tcx.interners.type_.lock_shards();
2027+
let types = shards.iter().flat_map(|shard| shard.keys());
2028+
for &Interned(t) in types {
20272029
let variant = match t.sty {
20282030
ty::Bool | ty::Char | ty::Int(..) | ty::Uint(..) |
20292031
ty::Float(..) | ty::Str | ty::Never => continue,
@@ -2074,11 +2076,11 @@ impl<'tcx> TyCtxt<'tcx> {
20742076
Generator, GeneratorWitness, Dynamic, Closure, Tuple, Bound,
20752077
Param, Infer, UnnormalizedProjection, Projection, Opaque, Foreign);
20762078

2077-
println!("InternalSubsts interner: #{}", self.interners.substs.borrow().len());
2078-
println!("Region interner: #{}", self.interners.region.borrow().len());
2079-
println!("Stability interner: #{}", self.stability_interner.borrow().len());
2080-
println!("Allocation interner: #{}", self.allocation_interner.borrow().len());
2081-
println!("Layout interner: #{}", self.layout_interner.borrow().len());
2079+
println!("InternalSubsts interner: #{}", self.interners.substs.len());
2080+
println!("Region interner: #{}", self.interners.region.len());
2081+
println!("Stability interner: #{}", self.stability_interner.len());
2082+
println!("Allocation interner: #{}", self.allocation_interner.len());
2083+
println!("Layout interner: #{}", self.layout_interner.len());
20822084
}
20832085
}
20842086

@@ -2207,7 +2209,7 @@ macro_rules! intern_method {
22072209
pub fn $method(self, v: $alloc) -> &$lt_tcx $ty {
22082210
let key = ($alloc_to_key)(&v);
22092211

2210-
self.interners.$name.borrow_mut().intern_ref(key, || {
2212+
self.interners.$name.intern_ref(key, || {
22112213
Interned($alloc_method(&self.interners.arena, v))
22122214

22132215
}).0

src/librustc_data_structures/interner.rs

-58
This file was deleted.

src/librustc_data_structures/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ pub mod flock;
7777
pub mod fx;
7878
pub mod graph;
7979
pub mod indexed_vec;
80-
pub mod interner;
8180
pub mod jobserver;
8281
pub mod obligation_forest;
8382
pub mod owning_ref;
@@ -89,6 +88,7 @@ pub use ena::snapshot_vec;
8988
pub mod sorted_map;
9089
#[macro_use] pub mod stable_hasher;
9190
pub mod sync;
91+
pub mod sharded;
9292
pub mod tiny_list;
9393
pub mod thin_vec;
9494
pub mod transitive_relation;
+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use std::hash::{Hasher, Hash};
2+
use std::mem;
3+
use std::borrow::Borrow;
4+
use std::collections::hash_map::RawEntryMut;
5+
use crate::fx::{FxHasher, FxHashMap};
6+
use crate::sync::{Lock, LockGuard};
7+
8+
#[derive(Clone, Default)]
9+
#[cfg_attr(parallel_compiler, repr(align(64)))]
10+
struct CacheAligned<T>(T);
11+
12+
#[cfg(parallel_compiler)]
13+
// 32 shards is sufficient to reduce contention on an 8-core Ryzen 7 1700,
14+
// but this should be tested on higher core count CPUs. How the `Sharded` type gets used
15+
// may also affect the ideal nunber of shards.
16+
const SHARD_BITS: usize = 5;
17+
18+
#[cfg(not(parallel_compiler))]
19+
const SHARD_BITS: usize = 0;
20+
21+
const SHARDS: usize = 1 << SHARD_BITS;
22+
23+
/// An array of cache-line aligned inner locked structures with convenience methods.
24+
#[derive(Clone)]
25+
pub struct Sharded<T> {
26+
shards: [CacheAligned<Lock<T>>; SHARDS],
27+
}
28+
29+
impl<T: Default> Default for Sharded<T> {
30+
#[inline]
31+
fn default() -> Self {
32+
let mut shards: mem::MaybeUninit<[CacheAligned<Lock<T>>; SHARDS]> =
33+
mem::MaybeUninit::uninit();
34+
let first = shards.as_mut_ptr() as *mut CacheAligned<Lock<T>>;
35+
unsafe {
36+
for i in 0..SHARDS {
37+
first.add(i).write(CacheAligned(Lock::new(T::default())));
38+
}
39+
Sharded {
40+
shards: shards.assume_init(),
41+
}
42+
}
43+
}
44+
}
45+
46+
impl<T> Sharded<T> {
47+
#[inline]
48+
pub fn get_shard_by_value<K: Hash + ?Sized>(&self, val: &K) -> &Lock<T> {
49+
if SHARDS == 1 {
50+
&self.shards[0].0
51+
} else {
52+
self.get_shard_by_hash(make_hash(val))
53+
}
54+
}
55+
56+
#[inline]
57+
pub fn get_shard_by_hash(&self, hash: u64) -> &Lock<T> {
58+
let hash_len = mem::size_of::<usize>();
59+
// Ignore the top 7 bits as hashbrown uses these and get the next SHARD_BITS highest bits.
60+
// hashbrown also uses the lowest bits, so we can't use those
61+
let bits = (hash >> (hash_len * 8 - 7 - SHARD_BITS)) as usize;
62+
let i = bits % SHARDS;
63+
&self.shards[i].0
64+
}
65+
66+
pub fn lock_shards(&self) -> Vec<LockGuard<'_, T>> {
67+
(0..SHARDS).map(|i| self.shards[i].0.lock()).collect()
68+
}
69+
70+
pub fn try_lock_shards(&self) -> Option<Vec<LockGuard<'_, T>>> {
71+
(0..SHARDS).map(|i| self.shards[i].0.try_lock()).collect()
72+
}
73+
}
74+
75+
pub type ShardedHashMap<K, V> = Sharded<FxHashMap<K, V>>;
76+
77+
impl<K: Eq + Hash, V> ShardedHashMap<K, V> {
78+
pub fn len(&self) -> usize {
79+
self.lock_shards().iter().map(|shard| shard.len()).sum()
80+
}
81+
}
82+
83+
impl<K: Eq + Hash + Copy> ShardedHashMap<K, ()> {
84+
#[inline]
85+
pub fn intern_ref<Q: ?Sized>(&self, value: &Q, make: impl FnOnce() -> K) -> K
86+
where K: Borrow<Q>,
87+
Q: Hash + Eq
88+
{
89+
let hash = make_hash(value);
90+
let mut shard = self.get_shard_by_hash(hash).lock();
91+
let entry = shard.raw_entry_mut().from_key_hashed_nocheck(hash, value);
92+
93+
match entry {
94+
RawEntryMut::Occupied(e) => *e.key(),
95+
RawEntryMut::Vacant(e) => {
96+
let v = make();
97+
e.insert_hashed_nocheck(hash, v, ());
98+
v
99+
}
100+
}
101+
}
102+
103+
#[inline]
104+
pub fn intern<Q>(&self, value: Q, make: impl FnOnce(Q) -> K) -> K
105+
where K: Borrow<Q>,
106+
Q: Hash + Eq
107+
{
108+
let hash = make_hash(&value);
109+
let mut shard = self.get_shard_by_hash(hash).lock();
110+
let entry = shard.raw_entry_mut().from_key_hashed_nocheck(hash, &value);
111+
112+
match entry {
113+
RawEntryMut::Occupied(e) => *e.key(),
114+
RawEntryMut::Vacant(e) => {
115+
let v = make(value);
116+
e.insert_hashed_nocheck(hash, v, ());
117+
v
118+
}
119+
}
120+
}
121+
}
122+
123+
#[inline]
124+
fn make_hash<K: Hash + ?Sized>(val: &K) -> u64 {
125+
let mut state = FxHasher::default();
126+
val.hash(&mut state);
127+
state.finish()
128+
}

0 commit comments

Comments
 (0)