Skip to content

Commit 36661b1

Browse files
committed
Build the dep-graph reverse index lazily, per DepKind
When a SerializedDepGraph is decoded, it built a fingerprint to index map for every DepKind covering every node. That inverse index is only consulted by node_to_index_opt, which runs for the nodes a session queries directly; the bulk of the graph is reached as edge targets by index and is never looked up by fingerprint, so most of those maps are never read. Replace the eager build with a counting sort that groups node indices into a contiguous range per DepKind, and build the fingerprint map for a kind only the first time a node of that kind is looked up. Decode no longer pays a hash-map insert per node, and kinds that are never looked up never build a map. The on-disk format is unchanged.
1 parent 1ce45a0 commit 36661b1

1 file changed

Lines changed: 88 additions & 21 deletions

File tree

compiler/rustc_middle/src/dep_graph/serialized.rs

Lines changed: 88 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@
4141
4242
use std::cell::RefCell;
4343
use std::cmp::max;
44-
use std::sync::Arc;
4544
use std::sync::atomic::Ordering;
45+
use std::sync::{Arc, OnceLock};
4646
use std::{iter, mem};
4747

4848
use rustc_data_structures::fingerprint::{Fingerprint, PackedFingerprint};
@@ -113,14 +113,51 @@ pub struct SerializedDepGraph {
113113
/// A flattened list of all edge targets in the graph, stored in the same
114114
/// varint encoding that we use on disk. Edge sources are implicit in edge_list_indices.
115115
edge_list_data: Vec<u8>,
116-
/// For each dep kind, stores a map from key fingerprints back to the index
117-
/// of the corresponding node. This is the inverse of `nodes`.
118-
index: Vec<UnhashMap<PackedFingerprint, SerializedDepNodeIndex>>,
116+
/// The lazily-built inverse of `nodes`: maps a [`DepNode`] back to its
117+
/// [`SerializedDepNodeIndex`] via the node's key fingerprint. See
118+
/// [`LazyNodeIndex`].
119+
index: LazyNodeIndex,
119120
/// The number of previous compilation sessions. This is used to generate
120121
/// unique anon dep nodes per session.
121122
session_count: u64,
122123
}
123124

125+
/// The inverse of [`SerializedDepGraph::nodes`], built lazily per [`DepKind`].
126+
///
127+
/// The node indices are grouped by `DepKind` into contiguous ranges of
128+
/// `nodes_by_kind` when the graph is decoded (a cheap counting sort). The actual
129+
/// fingerprint -> index map for a given `DepKind` is only built the first time a
130+
/// node of that kind is looked up by fingerprint.
131+
///
132+
/// This matters for incremental performance: marking the graph green in an
133+
/// unchanged session reaches the overwhelming majority of nodes by walking
134+
/// edges, which reference nodes by index (see `try_mark_previous_green`), not by
135+
/// fingerprint. Only the comparatively few nodes that the session queries
136+
/// directly are ever looked up here, and those cluster into a handful of
137+
/// `DepKind`s. Building a map for every kind up front (the bulk being per-`DefId`
138+
/// foreign/metadata reads that are never looked up) is therefore mostly wasted
139+
/// work.
140+
#[derive(Debug, Default)]
141+
struct LazyNodeIndex {
142+
/// All (non-`Null`) node indices, grouped into contiguous per-`DepKind`
143+
/// ranges described by `kinds`.
144+
nodes_by_kind: Vec<SerializedDepNodeIndex>,
145+
/// For each `DepKind`, the range of `nodes_by_kind` holding its node indices
146+
/// and the lazily-built fingerprint map over that range.
147+
kinds: Vec<LazyKindIndex>,
148+
}
149+
150+
#[derive(Debug, Default)]
151+
struct LazyKindIndex {
152+
/// Offset into `LazyNodeIndex::nodes_by_kind` of this kind's first node.
153+
start: u32,
154+
/// Number of nodes of this kind.
155+
len: u32,
156+
/// `key_fingerprint -> node index`, built from this kind's range on first
157+
/// lookup. Empty kinds (and kinds never looked up) never build a map.
158+
map: OnceLock<UnhashMap<PackedFingerprint, SerializedDepNodeIndex>>,
159+
}
160+
124161
impl SerializedDepGraph {
125162
#[inline]
126163
pub fn edge_targets_from(
@@ -151,7 +188,29 @@ impl SerializedDepGraph {
151188

152189
#[inline]
153190
pub fn node_to_index_opt(&self, dep_node: &DepNode) -> Option<SerializedDepNodeIndex> {
154-
self.index.get(dep_node.kind.as_usize())?.get(&dep_node.key_fingerprint).copied()
191+
let kind = self.index.kinds.get(dep_node.kind.as_usize())?;
192+
let map = kind.map.get_or_init(|| {
193+
let range = (kind.start as usize)..(kind.start as usize + kind.len as usize);
194+
let mut map = UnhashMap::with_capacity_and_hasher(kind.len as usize, Default::default());
195+
for &idx in &self.index.nodes_by_kind[range] {
196+
let node = &self.nodes[idx];
197+
if map.insert(node.key_fingerprint, idx).is_some()
198+
// Side effect nodes can legitimately share a fingerprint.
199+
&& node.kind != DepKind::SideEffect
200+
{
201+
panic!(
202+
"Error: A dep graph node ({:?}) does not have an unique index. \
203+
Running a clean build on a nightly compiler with \
204+
`-Z incremental-verify-ich` can help narrow down the issue for reporting. \
205+
A clean build may also work around the issue.\n
206+
DepNode: {node:?}",
207+
node.kind
208+
)
209+
}
210+
}
211+
map
212+
});
213+
map.get(&dep_node.key_fingerprint).copied()
155214
}
156215

157216
#[inline]
@@ -286,28 +345,36 @@ impl SerializedDepGraph {
286345
// end of the array. This padding ensure it doesn't.
287346
edge_list_data.extend(&[0u8; DEP_NODE_PAD]);
288347

289-
// Read the number of each dep kind and use it to create an hash map with a suitable size.
290-
let mut index: Vec<_> = (0..(DepKind::MAX + 1))
291-
.map(|_| UnhashMap::with_capacity_and_hasher(d.read_u32() as usize, Default::default()))
292-
.collect();
348+
// Read the number of nodes of each dep kind. These per-kind counts let us
349+
// group node indices by kind below; the fingerprint -> index maps
350+
// themselves are built lazily, per kind, on first lookup (see
351+
// `LazyNodeIndex`).
352+
let kind_counts: Vec<u32> = (0..(DepKind::MAX + 1)).map(|_| d.read_u32()).collect();
293353

294354
let session_count = d.read_u64();
295355

356+
// Counting sort: place every (non-`Null`) node index into a contiguous
357+
// per-kind range of `nodes_by_kind`, using the per-kind counts to compute
358+
// each kind's start offset. Padding gaps (`DepKind::Null`) are never
359+
// looked up by fingerprint, so we skip them.
360+
let mut kinds = Vec::with_capacity(kind_counts.len());
361+
let mut offset = 0u32;
362+
for &len in &kind_counts {
363+
kinds.push(LazyKindIndex { start: offset, len, map: OnceLock::new() });
364+
offset += len;
365+
}
366+
debug_assert_eq!(offset as usize, node_count);
367+
let mut nodes_by_kind = vec![SerializedDepNodeIndex::from_u32(0); offset as usize];
368+
let mut fill: Vec<u32> = kinds.iter().map(|k| k.start).collect();
296369
for (idx, node) in nodes.iter_enumerated() {
297-
if index[node.kind.as_usize()].insert(node.key_fingerprint, idx).is_some() {
298-
// Empty nodes and side effect nodes can have duplicates
299-
if node.kind != DepKind::Null && node.kind != DepKind::SideEffect {
300-
let kind = node.kind;
301-
panic!(
302-
"Error: A dep graph node ({kind:?}) does not have an unique index. \
303-
Running a clean build on a nightly compiler with \
304-
`-Z incremental-verify-ich` can help narrow down the issue for reporting. \
305-
A clean build may also work around the issue.\n
306-
DepNode: {node:?}"
307-
)
308-
}
370+
if node.kind == DepKind::Null {
371+
continue;
309372
}
373+
let k = node.kind.as_usize();
374+
nodes_by_kind[fill[k] as usize] = idx;
375+
fill[k] += 1;
310376
}
377+
let index = LazyNodeIndex { nodes_by_kind, kinds };
311378

312379
Arc::new(SerializedDepGraph {
313380
nodes,

0 commit comments

Comments
 (0)