Skip to content

Commit 2d0e8e3

Browse files
committed
Bucket dep-graph nodes by kind during decode instead of a second pass
The reverse index groups nodes by DepKind so each kind's fingerprint map can be built from a contiguous range. That grouping was a counting sort that ran as a separate pass over the decoded nodes, re-reading every node after the main decode loop had already visited it. Move the per-kind node counts into the fixed-size trailer so the decoder can read them up front, lay out the per-kind ranges before the main loop, and place each node into its range as it is decoded. This drops the separate pass.
1 parent 22bf345 commit 2d0e8e3

1 file changed

Lines changed: 41 additions & 38 deletions

File tree

compiler/rustc_middle/src/dep_graph/serialized.rs

Lines changed: 41 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -315,19 +315,28 @@ impl SerializedDepGraph {
315315

316316
// `node_max` is the number of indices including empty nodes while `node_count`
317317
// is the number of actually encoded nodes.
318-
let (node_max, node_count, edge_count) =
319-
d.with_position(d.len() - 3 * IntEncodedWithFixedSize::ENCODED_SIZE, |d| {
318+
//
319+
// The trailer holds the fixed-size node/edge counts followed by the per-kind
320+
// node counts, so all of them can be read by seeking from the end of the file.
321+
// Reading the per-kind counts up front lets us bucket nodes by kind during the
322+
// single decode pass below, rather than in a separate counting-sort pass.
323+
let kind_count = DepKind::MAX as usize + 1;
324+
let trailer_len = (3 + kind_count) * IntEncodedWithFixedSize::ENCODED_SIZE;
325+
let (node_max, node_count, edge_count, kind_counts) =
326+
d.with_position(d.len() - trailer_len, |d| {
320327
debug!("position: {:?}", d.position());
321328
let node_max = IntEncodedWithFixedSize::decode(d).0 as usize;
322329
let node_count = IntEncodedWithFixedSize::decode(d).0 as usize;
323330
let edge_count = IntEncodedWithFixedSize::decode(d).0 as usize;
324-
(node_max, node_count, edge_count)
331+
let kind_counts: Vec<u32> =
332+
(0..kind_count).map(|_| IntEncodedWithFixedSize::decode(d).0 as u32).collect();
333+
(node_max, node_count, edge_count, kind_counts)
325334
});
326335
debug!("position: {:?}", d.position());
327336

328337
debug!(?node_count, ?edge_count);
329338

330-
let graph_bytes = d.len() - (3 * IntEncodedWithFixedSize::ENCODED_SIZE) - d.position();
339+
let graph_bytes = d.len() - trailer_len - d.position();
331340

332341
let mut nodes = IndexVec::from_elem_n(
333342
DepNode {
@@ -352,6 +361,19 @@ impl SerializedDepGraph {
352361
let mut edge_list_data =
353362
Vec::with_capacity(graph_bytes - node_count * size_of::<SerializedNodeHeader>());
354363

364+
// Lay out the contiguous per-`DepKind` ranges of `nodes_by_kind` from the
365+
// counts read above, so each node can be placed into its kind's range as it
366+
// is decoded below. `fill[k]` is the next free slot in kind `k`'s range.
367+
let mut kinds = Vec::with_capacity(kind_count);
368+
let mut offset = 0u32;
369+
for &len in &kind_counts {
370+
kinds.push(LazyKindIndex { start: offset, len, map: OnceLock::new() });
371+
offset += len;
372+
}
373+
debug_assert_eq!(offset as usize, node_count);
374+
let mut nodes_by_kind = vec![None; node_count];
375+
let mut fill: Vec<u32> = kinds.iter().map(|k| k.start).collect();
376+
355377
for _ in 0..node_count {
356378
// Decode the header for this edge; the header packs together as many of the fixed-size
357379
// fields as possible to limit the number of times we update decoder state.
@@ -364,6 +386,12 @@ impl SerializedDepGraph {
364386
assert!(node_header.node().kind != DepKind::Null && node.kind == DepKind::Null);
365387
*node = node_header.node();
366388

389+
// Place this node into its kind's range for the lazy reverse index. Only
390+
// encoded (non-`Null`) nodes reach this loop, so each fills one slot once.
391+
let k = node.kind.as_usize();
392+
nodes_by_kind[fill[k] as usize] = Some(index);
393+
fill[k] += 1;
394+
367395
value_fingerprints[index] = node_header.value_fingerprint();
368396

369397
// If the length of this node's edge list is small, the length is stored in the header.
@@ -388,36 +416,9 @@ impl SerializedDepGraph {
388416
// end of the array. This padding ensure it doesn't.
389417
edge_list_data.extend(&[0u8; DEP_NODE_PAD]);
390418

391-
// Read the number of nodes of each dep kind, and perform
392-
// counting sort for `LazyNodeIndex`.
393-
let mut kinds = Vec::with_capacity(DepKind::MAX as usize + 1);
394-
let mut offset = 0u32;
395-
for _ in 0..(DepKind::MAX + 1) {
396-
let len = d.read_u32();
397-
kinds.push(LazyKindIndex { start: offset, len, map: OnceLock::new() });
398-
offset += len;
399-
}
400-
debug_assert_eq!(offset as usize, node_count);
401-
402419
let session_count = d.read_u64();
403420

404-
// Counting sort: place each node index into its kind's range. `fill[k]`
405-
// points at the next free slot in kind `k`'s range, so a kind's nodes end
406-
// up contiguous. Slots start as `None` and are each filled exactly once
407-
// (the counts sum to the number of non-`Null` nodes).
408-
let mut nodes_by_kind = vec![None; node_count];
409-
let mut fill: Vec<u32> = kinds.iter().map(|k| k.start).collect();
410-
for (idx, node) in nodes.iter_enumerated() {
411-
// Unused indices from batch allocation stay `Null`; they carry no
412-
// encoded node and are never looked up by fingerprint, so skip them.
413-
if node.kind == DepKind::Null {
414-
continue;
415-
}
416-
let k = node.kind.as_usize();
417-
nodes_by_kind[fill[k] as usize] = Some(idx);
418-
fill[k] += 1;
419-
}
420-
// Each kind's range was filled exactly to its end.
421+
// Each kind's range was filled exactly to its end by the decode loop.
421422
debug_assert!(kinds.iter().zip(&fill).all(|(k, &f)| f == k.start + k.len));
422423
let reverse_index = LazyNodeIndex { nodes_by_kind, kinds };
423424

@@ -885,20 +886,22 @@ impl EncoderState {
885886
}
886887
}
887888

888-
// Encode the number of each dep kind encountered
889-
for count in kind_stats.iter() {
890-
count.encode(&mut encoder);
891-
}
892-
889+
// The session count is read sequentially right after the node data.
893890
self.previous.session_count.checked_add(1).unwrap().encode(&mut encoder);
894891

895892
debug!(?node_max, ?node_count, ?edge_count);
896893
debug!("position: {:?}", encoder.position());
894+
// Fixed-size trailer: the node/edge counts followed by the number of nodes of
895+
// each dep kind, all readable by seeking from the end of the file. The decoder
896+
// reads the per-kind counts up front to bucket nodes by kind in a single pass.
897897
IntEncodedWithFixedSize(node_max.try_into().unwrap()).encode(&mut encoder);
898898
IntEncodedWithFixedSize(node_count.try_into().unwrap()).encode(&mut encoder);
899899
IntEncodedWithFixedSize(edge_count.try_into().unwrap()).encode(&mut encoder);
900+
for count in kind_stats.iter() {
901+
IntEncodedWithFixedSize(*count as u64).encode(&mut encoder);
902+
}
900903
debug!("position: {:?}", encoder.position());
901-
// Drop the encoder so that nothing is written after the counts.
904+
// Drop the encoder so that nothing is written after the trailer.
902905
let result = encoder.finish();
903906
if let Ok(position) = result {
904907
// FIXME(rylev): we hardcode the dep graph file name so we

0 commit comments

Comments
 (0)