Skip to content

Commit 90f5eab

Browse files
committed
Auto merge of rust-lang#115747 - Zoxc:query-hashes, r=oli-obk
Optimize hash map operations in the query system This optimizes hash map operations in the query system by explicitly passing hashes and using more optimal operations. `find_or_find_insert_slot` in particular saves a hash table lookup over `entry`. It's not yet available in a safe API, but will be in rust-lang/hashbrown#466. <table><tr><td rowspan="2">Benchmark</td><td colspan="1"><b>Before</b></th><td colspan="2"><b>After</b></th></tr><tr><td align="right">Time</td><td align="right">Time</td><td align="right">%</th></tr><tr><td>🟣 <b>clap</b>:check</td><td align="right">1.6189s</td><td align="right">1.6129s</td><td align="right"> -0.37%</td></tr><tr><td>🟣 <b>hyper</b>:check</td><td align="right">0.2353s</td><td align="right">0.2337s</td><td align="right"> -0.67%</td></tr><tr><td>🟣 <b>regex</b>:check</td><td align="right">0.9344s</td><td align="right">0.9289s</td><td align="right"> -0.59%</td></tr><tr><td>🟣 <b>syn</b>:check</td><td align="right">1.4693s</td><td align="right">1.4652s</td><td align="right"> -0.28%</td></tr><tr><td>🟣 <b>syntex_syntax</b>:check</td><td align="right">5.6606s</td><td align="right">5.6439s</td><td align="right"> -0.30%</td></tr><tr><td>Total</td><td align="right">9.9185s</td><td align="right">9.8846s</td><td align="right"> -0.34%</td></tr><tr><td>Summary</td><td align="right">1.0000s</td><td align="right">0.9956s</td><td align="right"> -0.44%</td></tr></table> r? `@cjgillot`
2 parents b95aac6 + 93bfe39 commit 90f5eab

File tree

4 files changed

+48
-30
lines changed

4 files changed

+48
-30
lines changed

Diff for: Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -4306,6 +4306,7 @@ dependencies = [
43064306
name = "rustc_query_system"
43074307
version = "0.0.0"
43084308
dependencies = [
4309+
"hashbrown 0.15.2",
43094310
"parking_lot",
43104311
"rustc-rayon-core",
43114312
"rustc_abi",

Diff for: compiler/rustc_data_structures/src/sharded.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ impl<K: Eq + Hash + Copy + IntoPointer> ShardedHashMap<K, ()> {
256256
}
257257

258258
#[inline]
259-
fn make_hash<K: Hash + ?Sized>(val: &K) -> u64 {
259+
pub fn make_hash<K: Hash + ?Sized>(val: &K) -> u64 {
260260
let mut state = FxHasher::default();
261261
val.hash(&mut state);
262262
state.finish()

Diff for: compiler/rustc_query_system/Cargo.toml

+5
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,8 @@ rustc_span = { path = "../rustc_span" }
2424
smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
2525
tracing = "0.1"
2626
# tidy-alphabetical-end
27+
28+
[dependencies.hashbrown]
29+
version = "0.15.2"
30+
default-features = false
31+
features = ["nightly"] # for may_dangle

Diff for: compiler/rustc_query_system/src/query/plumbing.rs

+41-29
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
//! manage the caches, and so forth.
44
55
use std::cell::Cell;
6-
use std::collections::hash_map::Entry;
76
use std::fmt::Debug;
87
use std::hash::Hash;
98
use std::mem;
109

10+
use hashbrown::hash_table::Entry;
1111
use rustc_data_structures::fingerprint::Fingerprint;
12-
use rustc_data_structures::fx::FxHashMap;
13-
use rustc_data_structures::sharded::Sharded;
12+
use rustc_data_structures::sharded::{self, Sharded};
1413
use rustc_data_structures::stack::ensure_sufficient_stack;
1514
use rustc_data_structures::{outline, sync};
1615
use rustc_errors::{Diag, FatalError, StashKey};
@@ -25,8 +24,13 @@ use crate::query::caches::QueryCache;
2524
use crate::query::job::{QueryInfo, QueryJob, QueryJobId, QueryJobInfo, QueryLatch, report_cycle};
2625
use crate::query::{QueryContext, QueryMap, QueryStackFrame, SerializedDepNodeIndex};
2726

27+
#[inline]
28+
fn equivalent_key<K: Eq, V>(k: &K) -> impl Fn(&(K, V)) -> bool + '_ {
29+
move |x| x.0 == *k
30+
}
31+
2832
pub struct QueryState<K> {
29-
active: Sharded<FxHashMap<K, QueryResult>>,
33+
active: Sharded<hashbrown::HashTable<(K, QueryResult)>>,
3034
}
3135

3236
/// Indicates the state of a query for a given key in a query map.
@@ -159,7 +163,7 @@ where
159163
{
160164
/// Completes the query by updating the query cache with the `result`,
161165
/// signals the waiter and forgets the JobOwner, so it won't poison the query
162-
fn complete<C>(self, cache: &C, result: C::Value, dep_node_index: DepNodeIndex)
166+
fn complete<C>(self, cache: &C, key_hash: u64, result: C::Value, dep_node_index: DepNodeIndex)
163167
where
164168
C: QueryCache<Key = K>,
165169
{
@@ -174,16 +178,17 @@ where
174178
cache.complete(key, result, dep_node_index);
175179

176180
let job = {
177-
let val = {
178-
// don't keep the lock during the `unwrap()` of the retrieved value, or we taint the
179-
// underlying shard.
180-
// since unwinding also wants to look at this map, this can also prevent a double
181-
// panic.
182-
let mut lock = state.active.lock_shard_by_value(&key);
183-
lock.remove(&key)
184-
};
185-
val.unwrap().expect_job()
181+
// don't keep the lock during the `unwrap()` of the retrieved value, or we taint the
182+
// underlying shard.
183+
// since unwinding also wants to look at this map, this can also prevent a double
184+
// panic.
185+
let mut shard = state.active.lock_shard_by_hash(key_hash);
186+
match shard.find_entry(key_hash, equivalent_key(&key)) {
187+
Err(_) => None,
188+
Ok(occupied) => Some(occupied.remove().0.1),
189+
}
186190
};
191+
let job = job.expect("active query job entry").expect_job();
187192

188193
job.signal_complete();
189194
}
@@ -199,11 +204,16 @@ where
199204
// Poison the query so jobs waiting on it panic.
200205
let state = self.state;
201206
let job = {
202-
let mut shard = state.active.lock_shard_by_value(&self.key);
203-
let job = shard.remove(&self.key).unwrap().expect_job();
204-
205-
shard.insert(self.key, QueryResult::Poisoned);
206-
job
207+
let key_hash = sharded::make_hash(&self.key);
208+
let mut shard = state.active.lock_shard_by_hash(key_hash);
209+
match shard.find_entry(key_hash, equivalent_key(&self.key)) {
210+
Err(_) => panic!(),
211+
Ok(occupied) => {
212+
let ((key, value), vacant) = occupied.remove();
213+
vacant.insert((key, QueryResult::Poisoned));
214+
value.expect_job()
215+
}
216+
}
207217
};
208218
// Also signal the completion of the job, so waiters
209219
// will continue execution.
@@ -283,11 +293,11 @@ where
283293
outline(|| {
284294
// We didn't find the query result in the query cache. Check if it was
285295
// poisoned due to a panic instead.
286-
let lock = query.query_state(qcx).active.get_shard_by_value(&key).lock();
287-
288-
match lock.get(&key) {
296+
let key_hash = sharded::make_hash(&key);
297+
let shard = query.query_state(qcx).active.lock_shard_by_hash(key_hash);
298+
match shard.find(key_hash, equivalent_key(&key)) {
289299
// The query we waited on panicked. Continue unwinding here.
290-
Some(QueryResult::Poisoned) => FatalError.raise(),
300+
Some((_, QueryResult::Poisoned)) => FatalError.raise(),
291301
_ => panic!(
292302
"query '{}' result must be in the cache or the query must be poisoned after a wait",
293303
query.name()
@@ -318,7 +328,8 @@ where
318328
Qcx: QueryContext,
319329
{
320330
let state = query.query_state(qcx);
321-
let mut state_lock = state.active.lock_shard_by_value(&key);
331+
let key_hash = sharded::make_hash(&key);
332+
let mut state_lock = state.active.lock_shard_by_hash(key_hash);
322333

323334
// For the parallel compiler we need to check both the query cache and query state structures
324335
// while holding the state lock to ensure that 1) the query has not yet completed and 2) the
@@ -335,21 +346,21 @@ where
335346

336347
let current_job_id = qcx.current_query_job();
337348

338-
match state_lock.entry(key) {
349+
match state_lock.entry(key_hash, equivalent_key(&key), |(k, _)| sharded::make_hash(k)) {
339350
Entry::Vacant(entry) => {
340351
// Nothing has computed or is computing the query, so we start a new job and insert it in the
341352
// state map.
342353
let id = qcx.next_job_id();
343354
let job = QueryJob::new(id, span, current_job_id);
344-
entry.insert(QueryResult::Started(job));
355+
entry.insert((key, QueryResult::Started(job)));
345356

346357
// Drop the lock before we start executing the query
347358
drop(state_lock);
348359

349-
execute_job::<_, _, INCR>(query, qcx, state, key, id, dep_node)
360+
execute_job::<_, _, INCR>(query, qcx, state, key, key_hash, id, dep_node)
350361
}
351362
Entry::Occupied(mut entry) => {
352-
match entry.get_mut() {
363+
match &mut entry.get_mut().1 {
353364
QueryResult::Started(job) => {
354365
if sync::is_dyn_thread_safe() {
355366
// Get the latch out
@@ -380,6 +391,7 @@ fn execute_job<Q, Qcx, const INCR: bool>(
380391
qcx: Qcx,
381392
state: &QueryState<Q::Key>,
382393
key: Q::Key,
394+
key_hash: u64,
383395
id: QueryJobId,
384396
dep_node: Option<DepNode>,
385397
) -> (Q::Value, Option<DepNodeIndex>)
@@ -440,7 +452,7 @@ where
440452
}
441453
}
442454
}
443-
job_owner.complete(cache, result, dep_node_index);
455+
job_owner.complete(cache, key_hash, result, dep_node_index);
444456

445457
(result, Some(dep_node_index))
446458
}

0 commit comments

Comments
 (0)