Skip to content

Commit

Permalink
perf(degree_lowering): cache node-degrees
Browse files Browse the repository at this point in the history
Cache node degrees in the `pick_node_to_substitute` function which was
found to be the bottle-neck in the build script. This caching shaves off
38 % of the time it takes to degree-lower, and 30 % of total build time
of Triton VM.

Measurements on my machine (mjolnir):
degree-lowering step in build script:
Before caching degrees:
- 22.024172299s
- 21.938811734s

After caching:
- 13.657397858s
- 13.753980057s

Total time to build triton-vm after change to
`triton-constraint-circuit/src/lib.rs`:
Before caching degrees:
- 26.13s
- 26.74s

After caching:
- 18.63s
- 18.65s
  • Loading branch information
Sword-Smith committed Sep 19, 2024
1 parent 5e16b08 commit 6496a74
Showing 1 changed file with 20 additions and 20 deletions.
40 changes: 20 additions & 20 deletions triton-constraint-circuit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -781,58 +781,58 @@ impl<II: InputIndicator> ConstraintCircuitMonad<II> {
multicircuit: &[ConstraintCircuitMonad<II>],
target_degree: isize,
) -> usize {
// The relevant fields of a ConstraintCircuit.
// Avoids interior mutability in a HashSet, which is a foot gun.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
struct PartialNodeInfo {
id: usize,
degree: isize,
}

assert!(!multicircuit.is_empty());
let multicircuit = multicircuit
.iter()
.map(|c| c.clone().consume())
.collect_vec();

// Computing all node degree is slow; this cache de-duplicates work.
let node_degrees = Self::all_nodes_in_multicircuit(&multicircuit)
.into_iter()
.map(|node| (node.id, node.degree()))
.collect::<HashMap<_, _>>();

// Only nodes with degree > target_degree need changing.
let high_degree_nodes = Self::all_nodes_in_multicircuit(&multicircuit)
.into_iter()
.filter(|node| node_degrees[&node.id] > target_degree)
.unique()
.filter(|node| node.degree() > target_degree)
.collect_vec();

// Collect all candidates for substitution, i.e., descendents of high_degree_nodes
// with degree <= target_degree.
// Substituting a node of degree 1 is both pointless and can lead to infinite iteration.
let low_degree_nodes = Self::all_nodes_in_multicircuit(&high_degree_nodes)
.into_iter()
.filter(|node| 1 < node.degree() && node.degree() <= target_degree)
.map(|node| PartialNodeInfo {
id: node.id,
degree: node.degree(),
})
.filter(|node| 1 < node_degrees[&node.id] && node_degrees[&node.id] <= target_degree)
.map(|node| node.id)
.collect_vec();

// If the resulting list is empty, there is no way forward. Stop – panic time!
assert!(!low_degree_nodes.is_empty(), "Cannot lower degree.");

// Of the remaining nodes, keep the ones occurring the most often.
let mut nodes_and_occurrences = HashMap::new();
for node in &low_degree_nodes {
for node in low_degree_nodes {
*nodes_and_occurrences.entry(node).or_insert(0) += 1;
}
let max_occurrences = nodes_and_occurrences.iter().map(|(_, &c)| c).max().unwrap();
nodes_and_occurrences.retain(|_, &mut count| count == max_occurrences);
let mut candidate_nodes = nodes_and_occurrences.keys().copied().collect_vec();
let mut candidate_node_ids = nodes_and_occurrences.keys().copied().collect_vec();

// If there are still multiple nodes, pick the one with the highest degree.
let max_degree = candidate_nodes.iter().map(|n| n.degree).max().unwrap();
candidate_nodes.retain(|node| node.degree == max_degree);
let max_degree = candidate_node_ids
.iter()
.map(|node_id| node_degrees[node_id])
.max()
.unwrap();
candidate_node_ids.retain(|node_id| node_degrees[node_id] == max_degree);

candidate_node_ids.sort_unstable();

// If there are still multiple nodes, pick any one – but deterministically so.
candidate_nodes.sort_unstable_by_key(|node| node.id);
candidate_nodes[0].id
candidate_node_ids.into_iter().min().unwrap()
}

/// Returns all nodes used in the multicircuit.
Expand Down

0 comments on commit 6496a74

Please sign in to comment.