Skip to content

Commit e63878f

Browse files
authored
perf: Faster singleton SiblingSubgraph construction (#1654)
Wraps the topoConvexChecker used by `SiblingSubgraph` in a `OnceCell` so we can delay initialization until it's needed. This lets us avoid traversing the graph (and computing a topological order) when creating sibling subgraphs with zero or one nodes. See benchmark results: ``` $ critcmp before after -f '.*subgraph' group after before ----- ----- ------ singleton_subgraph/10 1.00 3.0±0.08µs ? ?/sec 2.71 8.1±0.18µs ? ?/sec singleton_subgraph/100 1.00 4.8±0.07µs ? ?/sec 9.81 47.4±1.16µs ? ?/sec singleton_subgraph/1000 1.00 23.2±1.86µs ? ?/sec 18.94 439.3±17.04µs ? ?/sec multinode_subgraph/10 1.01 17.9±0.25µs ? ?/sec 1.00 17.7±0.29µs ? ?/sec multinode_subgraph/100 1.01 170.0±3.72µs ? ?/sec 1.00 168.5±3.04µs ? ?/sec multinode_subgraph/1000 1.02 2.4±0.02ms ? ?/sec 1.00 2.3±0.04ms ? ?/sec ``` `singleton_subgraph` creates a single-node subgraph in a region with around `k * 3` nodes. `multinode_subgraph` creates a subgraph with 1/3rd of the nodes for the same region. This PR is quite noisy since it's adding those two new benchmarks, and tidying up the files in the process. drive-by: Add `bench = false` for all the targets in the workspace. Otherwise, the auto-added test harness threw errors when passing criterion flags to `cargo bench`.
1 parent 0c430d1 commit e63878f

File tree

11 files changed

+252
-135
lines changed

11 files changed

+252
-135
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ repos:
8181
- id: cargo-clippy
8282
name: cargo clippy
8383
description: Run clippy lints with `cargo clippy`.
84-
entry: cargo clippy --all-features --workspace -- -D warnings
84+
entry: cargo clippy --all-targets --all-features --workspace -- -D warnings
8585
language: system
8686
files: \.rs$
8787
pass_filenames: false

hugr-cli/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ description = "Compiler passes for Quantinuum's HUGR"
1212
keywords = ["Quantum", "Quantinuum"]
1313
categories = ["compilers"]
1414

15+
[lib]
16+
bench = false
17+
1518
[dependencies]
1619
clap = { workspace = true, features = ["derive"] }
1720
clap-verbosity-flag.workspace = true
@@ -42,3 +45,4 @@ rstest.workspace = true
4245
name = "hugr"
4346
path = "src/main.rs"
4447
doc = false
48+
bench = false

hugr-core/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ extension_inference = []
2121
declarative = ["serde_yaml"]
2222
model_unstable = ["hugr-model"]
2323

24+
[lib]
25+
bench = false
26+
2427
[[test]]
2528
name = "model"
2629
required-features = ["model_unstable"]

hugr-core/src/hugr/views/sibling_subgraph.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
//! while the former provide views for subgraphs within a single level of the
1010
//! hierarchy.
1111
12+
use std::cell::OnceCell;
1213
use std::collections::HashSet;
1314
use std::mem;
1415

@@ -453,15 +454,24 @@ fn combine_in_out<'a>(
453454
///
454455
/// This can be used when constructing multiple sibling subgraphs to speed up
455456
/// convexity checking.
456-
pub struct TopoConvexChecker<'g, Base: 'g + HugrView>(
457-
portgraph::algorithms::TopoConvexChecker<Base::Portgraph<'g>>,
458-
);
457+
pub struct TopoConvexChecker<'g, Base: 'g + HugrView> {
458+
base: &'g Base,
459+
checker: OnceCell<portgraph::algorithms::TopoConvexChecker<Base::Portgraph<'g>>>,
460+
}
459461

460462
impl<'g, Base: HugrView> TopoConvexChecker<'g, Base> {
461463
/// Create a new convexity checker.
462464
pub fn new(base: &'g Base) -> Self {
463-
let pg = base.portgraph();
464-
Self(portgraph::algorithms::TopoConvexChecker::new(pg))
465+
Self {
466+
base,
467+
checker: OnceCell::new(),
468+
}
469+
}
470+
471+
/// Returns the portgraph convexity checker, initializing it if necessary.
472+
fn get_checker(&self) -> &portgraph::algorithms::TopoConvexChecker<Base::Portgraph<'g>> {
473+
self.checker
474+
.get_or_init(|| portgraph::algorithms::TopoConvexChecker::new(self.base.portgraph()))
465475
}
466476
}
467477

@@ -472,7 +482,13 @@ impl<'g, Base: HugrView> ConvexChecker for TopoConvexChecker<'g, Base> {
472482
inputs: impl IntoIterator<Item = portgraph::PortIndex>,
473483
outputs: impl IntoIterator<Item = portgraph::PortIndex>,
474484
) -> bool {
475-
self.0.is_convex(nodes, inputs, outputs)
485+
let mut nodes = nodes.into_iter().multipeek();
486+
// If the node iterator contains less than two nodes, the subgraph is
487+
// trivially convex.
488+
if nodes.peek().is_none() || nodes.peek().is_none() {
489+
return true;
490+
};
491+
self.get_checker().is_convex(nodes, inputs, outputs)
476492
}
477493
}
478494

hugr-model/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ homepage.workspace = true
1212
repository.workspace = true
1313
license.workspace = true
1414

15+
[lib]
16+
bench = false
17+
1518
[dependencies]
1619
bumpalo = { workspace = true, features = ["collections"] }
1720
capnp = "0.20.1"

hugr-passes/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ description = "Compiler passes for Quantinuum's HUGR"
1212
keywords = ["Quantum", "Quantinuum"]
1313
categories = ["compilers"]
1414

15+
[lib]
16+
bench = false
17+
1518
[dependencies]
1619
hugr-core = { path = "../hugr-core", version = "0.13.3" }
1720
itertools = { workspace = true }

hugr/benches/bench_main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ use criterion::criterion_main;
77

88
criterion_main! {
99
benchmarks::hugr::benches,
10+
benchmarks::subgraph::benches,
1011
benchmarks::types::benches,
1112
}

hugr/benches/benchmarks/hugr.rs

Lines changed: 8 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,13 @@
11
#![allow(clippy::unit_arg)] // Required for black_box uses
22

3+
pub mod examples;
4+
35
use criterion::{black_box, criterion_group, AxisScale, BenchmarkId, Criterion, PlotConfiguration};
4-
use hugr::builder::{
5-
BuildError, CFGBuilder, Container, DFGBuilder, Dataflow, DataflowHugr, DataflowSubContainer,
6-
HugrBuilder, ModuleBuilder,
7-
};
8-
use hugr::extension::prelude::{BOOL_T, QB_T, USIZE_T};
9-
use hugr::extension::PRELUDE_REGISTRY;
10-
use hugr::ops::OpName;
6+
#[allow(unused)]
117
use hugr::std_extensions::arithmetic::float_ops::FLOAT_OPS_REGISTRY;
12-
use hugr::std_extensions::arithmetic::float_types::FLOAT64_TYPE;
13-
use hugr::types::Signature;
14-
use hugr::{type_row, Extension, Hugr};
15-
use lazy_static::lazy_static;
16-
pub fn simple_dfg_hugr() -> Hugr {
17-
let dfg_builder =
18-
DFGBuilder::new(Signature::new(type_row![BOOL_T], type_row![BOOL_T])).unwrap();
19-
let [i1] = dfg_builder.input_wires_arr();
20-
dfg_builder.finish_prelude_hugr_with_outputs([i1]).unwrap()
21-
}
22-
23-
pub fn simple_cfg_builder<T: AsMut<Hugr> + AsRef<Hugr>>(
24-
cfg_builder: &mut CFGBuilder<T>,
25-
) -> Result<(), BuildError> {
26-
let sum2_variants = vec![type_row![USIZE_T], type_row![USIZE_T]];
27-
let mut entry_b = cfg_builder.entry_builder(sum2_variants.clone(), type_row![])?;
28-
let entry = {
29-
let [inw] = entry_b.input_wires_arr();
30-
31-
let sum = entry_b.make_sum(1, sum2_variants, [inw])?;
32-
entry_b.finish_with_outputs(sum, [])?
33-
};
34-
let mut middle_b = cfg_builder
35-
.simple_block_builder(Signature::new(type_row![USIZE_T], type_row![USIZE_T]), 1)?;
36-
let middle = {
37-
let c = middle_b.add_load_const(hugr::ops::Value::unary_unit_sum());
38-
let [inw] = middle_b.input_wires_arr();
39-
middle_b.finish_with_outputs(c, [inw])?
40-
};
41-
let exit = cfg_builder.exit_block();
42-
cfg_builder.branch(&entry, 0, &middle)?;
43-
cfg_builder.branch(&middle, 0, &exit)?;
44-
cfg_builder.branch(&entry, 1, &exit)?;
45-
Ok(())
46-
}
8+
use hugr::Hugr;
479

48-
pub fn simple_cfg_hugr() -> Hugr {
49-
let mut cfg_builder =
50-
CFGBuilder::new(Signature::new(type_row![USIZE_T], type_row![USIZE_T])).unwrap();
51-
simple_cfg_builder(&mut cfg_builder).unwrap();
52-
cfg_builder.finish_prelude_hugr().unwrap()
53-
}
10+
pub use examples::{circuit, simple_cfg_hugr, simple_dfg_hugr};
5411

5512
trait Serializer {
5613
fn serialize(&self, hugr: &Hugr) -> Vec<u8>;
@@ -90,83 +47,6 @@ fn roundtrip(hugr: &Hugr, serializer: impl Serializer) -> Hugr {
9047
serializer.deserialize(&bytes)
9148
}
9249

93-
lazy_static! {
94-
static ref QUANTUM_EXT: Extension = {
95-
let mut extension = Extension::new(
96-
"bench.quantum".try_into().unwrap(),
97-
hugr::extension::Version::new(0, 0, 0),
98-
);
99-
100-
extension
101-
.add_op(
102-
OpName::new_inline("H"),
103-
"".into(),
104-
Signature::new_endo(QB_T),
105-
)
106-
.unwrap();
107-
extension
108-
.add_op(
109-
OpName::new_inline("Rz"),
110-
"".into(),
111-
Signature::new(type_row![QB_T, FLOAT64_TYPE], type_row![QB_T]),
112-
)
113-
.unwrap();
114-
115-
extension
116-
.add_op(
117-
OpName::new_inline("CX"),
118-
"".into(),
119-
Signature::new_endo(type_row![QB_T, QB_T]),
120-
)
121-
.unwrap();
122-
extension
123-
};
124-
}
125-
126-
pub fn circuit(layers: usize) -> Hugr {
127-
let h_gate = QUANTUM_EXT
128-
.instantiate_extension_op("H", [], &PRELUDE_REGISTRY)
129-
.unwrap();
130-
let cx_gate = QUANTUM_EXT
131-
.instantiate_extension_op("CX", [], &PRELUDE_REGISTRY)
132-
.unwrap();
133-
// let rz = QUANTUM_EXT
134-
// .instantiate_extension_op("Rz", [], &FLOAT_OPS_REGISTRY)
135-
// .unwrap();
136-
let signature =
137-
Signature::new_endo(type_row![QB_T, QB_T]).with_extension_delta(QUANTUM_EXT.name().clone());
138-
let mut module_builder = ModuleBuilder::new();
139-
let mut f_build = module_builder.define_function("main", signature).unwrap();
140-
141-
let wires: Vec<_> = f_build.input_wires().collect();
142-
143-
let mut linear = f_build.as_circuit(wires);
144-
145-
for _ in 0..layers {
146-
linear
147-
.append(h_gate.clone(), [0])
148-
.unwrap()
149-
.append(cx_gate.clone(), [0, 1])
150-
.unwrap()
151-
.append(cx_gate.clone(), [1, 0])
152-
.unwrap();
153-
154-
// TODO: Currently left out because we can not represent constants in the model
155-
// let angle = linear.add_constant(ConstF64::new(0.5));
156-
// linear
157-
// .append_and_consume(
158-
// rz.clone(),
159-
// [CircuitUnit::Linear(0), CircuitUnit::Wire(angle)],
160-
// )
161-
// .unwrap();
162-
}
163-
164-
let outs = linear.finish();
165-
f_build.finish_with_outputs(outs).unwrap();
166-
167-
module_builder.finish_hugr(&FLOAT_OPS_REGISTRY).unwrap()
168-
}
169-
17050
fn bench_builder(c: &mut Criterion) {
17151
let mut group = c.benchmark_group("builder");
17252
group.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));
@@ -187,7 +67,7 @@ fn bench_serialization(c: &mut Criterion) {
18767
group.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));
18868
for size in [0, 1, 10, 100, 1000].iter() {
18969
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
190-
let h = circuit(size);
70+
let h = circuit(size).0;
19171
b.iter(|| {
19272
black_box(roundtrip(&h, JsonSer));
19373
});
@@ -199,7 +79,7 @@ fn bench_serialization(c: &mut Criterion) {
19979
group.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));
20080
for size in [0, 1, 10, 100, 1000].iter() {
20181
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
202-
let h = circuit(size);
82+
let h = circuit(size).0;
20383
b.iter(|| {
20484
black_box(JsonSer.serialize(&h));
20585
});
@@ -213,7 +93,7 @@ fn bench_serialization(c: &mut Criterion) {
21393
group.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic));
21494
for size in [0, 1, 10, 100, 1000].iter() {
21595
group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| {
216-
let h = circuit(size);
96+
let h = circuit(size).0;
21797
b.iter(|| {
21898
black_box(roundtrip(&h, CapnpSer));
21999
});

0 commit comments

Comments
 (0)