From 9b86b4c133e2cc5c3e1f57c6113b5cdb6aaa5108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20Borgna?= Date: Wed, 13 Nov 2024 14:50:04 +0000 Subject: [PATCH 1/4] perf: Benchmark SiblingSubgraph construction --- hugr/benches/bench_main.rs | 1 + hugr/benches/benchmarks/hugr.rs | 136 ++------------------- hugr/benches/benchmarks/hugr/examples.rs | 143 +++++++++++++++++++++++ hugr/benches/benchmarks/mod.rs | 1 + hugr/benches/benchmarks/subgraph.rs | 63 ++++++++++ 5 files changed, 216 insertions(+), 128 deletions(-) create mode 100644 hugr/benches/benchmarks/hugr/examples.rs create mode 100644 hugr/benches/benchmarks/subgraph.rs diff --git a/hugr/benches/bench_main.rs b/hugr/benches/bench_main.rs index bc0619c48..1c115a18b 100644 --- a/hugr/benches/bench_main.rs +++ b/hugr/benches/bench_main.rs @@ -7,5 +7,6 @@ use criterion::criterion_main; criterion_main! { benchmarks::hugr::benches, + benchmarks::subgraph::benches, benchmarks::types::benches, } diff --git a/hugr/benches/benchmarks/hugr.rs b/hugr/benches/benchmarks/hugr.rs index 4139e882c..6bdbe5938 100644 --- a/hugr/benches/benchmarks/hugr.rs +++ b/hugr/benches/benchmarks/hugr.rs @@ -1,56 +1,13 @@ #![allow(clippy::unit_arg)] // Required for black_box uses +pub mod examples; + use criterion::{black_box, criterion_group, AxisScale, BenchmarkId, Criterion, PlotConfiguration}; -use hugr::builder::{ - BuildError, CFGBuilder, Container, DFGBuilder, Dataflow, DataflowHugr, DataflowSubContainer, - HugrBuilder, ModuleBuilder, -}; -use hugr::extension::prelude::{BOOL_T, QB_T, USIZE_T}; -use hugr::extension::PRELUDE_REGISTRY; -use hugr::ops::OpName; +#[allow(unused)] use hugr::std_extensions::arithmetic::float_ops::FLOAT_OPS_REGISTRY; -use hugr::std_extensions::arithmetic::float_types::FLOAT64_TYPE; -use hugr::types::Signature; -use hugr::{type_row, Extension, Hugr}; -use lazy_static::lazy_static; -pub fn simple_dfg_hugr() -> Hugr { - let dfg_builder = - DFGBuilder::new(Signature::new(type_row![BOOL_T], type_row![BOOL_T])).unwrap(); - let [i1] = dfg_builder.input_wires_arr(); - dfg_builder.finish_prelude_hugr_with_outputs([i1]).unwrap() -} - -pub fn simple_cfg_builder + AsRef>( - cfg_builder: &mut CFGBuilder, -) -> Result<(), BuildError> { - let sum2_variants = vec![type_row![USIZE_T], type_row![USIZE_T]]; - let mut entry_b = cfg_builder.entry_builder(sum2_variants.clone(), type_row![])?; - let entry = { - let [inw] = entry_b.input_wires_arr(); - - let sum = entry_b.make_sum(1, sum2_variants, [inw])?; - entry_b.finish_with_outputs(sum, [])? - }; - let mut middle_b = cfg_builder - .simple_block_builder(Signature::new(type_row![USIZE_T], type_row![USIZE_T]), 1)?; - let middle = { - let c = middle_b.add_load_const(hugr::ops::Value::unary_unit_sum()); - let [inw] = middle_b.input_wires_arr(); - middle_b.finish_with_outputs(c, [inw])? - }; - let exit = cfg_builder.exit_block(); - cfg_builder.branch(&entry, 0, &middle)?; - cfg_builder.branch(&middle, 0, &exit)?; - cfg_builder.branch(&entry, 1, &exit)?; - Ok(()) -} +use hugr::Hugr; -pub fn simple_cfg_hugr() -> Hugr { - let mut cfg_builder = - CFGBuilder::new(Signature::new(type_row![USIZE_T], type_row![USIZE_T])).unwrap(); - simple_cfg_builder(&mut cfg_builder).unwrap(); - cfg_builder.finish_prelude_hugr().unwrap() -} +pub use examples::{circuit, simple_cfg_hugr, simple_dfg_hugr}; trait Serializer { fn serialize(&self, hugr: &Hugr) -> Vec; @@ -90,83 +47,6 @@ fn roundtrip(hugr: &Hugr, serializer: impl Serializer) -> Hugr { serializer.deserialize(&bytes) } -lazy_static! { - static ref QUANTUM_EXT: Extension = { - let mut extension = Extension::new( - "bench.quantum".try_into().unwrap(), - hugr::extension::Version::new(0, 0, 0), - ); - - extension - .add_op( - OpName::new_inline("H"), - "".into(), - Signature::new_endo(QB_T), - ) - .unwrap(); - extension - .add_op( - OpName::new_inline("Rz"), - "".into(), - Signature::new(type_row![QB_T, FLOAT64_TYPE], type_row![QB_T]), - ) - .unwrap(); - - extension - .add_op( - OpName::new_inline("CX"), - "".into(), - Signature::new_endo(type_row![QB_T, QB_T]), - ) - .unwrap(); - extension - }; -} - -pub fn circuit(layers: usize) -> Hugr { - let h_gate = QUANTUM_EXT - .instantiate_extension_op("H", [], &PRELUDE_REGISTRY) - .unwrap(); - let cx_gate = QUANTUM_EXT - .instantiate_extension_op("CX", [], &PRELUDE_REGISTRY) - .unwrap(); - // let rz = QUANTUM_EXT - // .instantiate_extension_op("Rz", [], &FLOAT_OPS_REGISTRY) - // .unwrap(); - let signature = - Signature::new_endo(type_row![QB_T, QB_T]).with_extension_delta(QUANTUM_EXT.name().clone()); - let mut module_builder = ModuleBuilder::new(); - let mut f_build = module_builder.define_function("main", signature).unwrap(); - - let wires: Vec<_> = f_build.input_wires().collect(); - - let mut linear = f_build.as_circuit(wires); - - for _ in 0..layers { - linear - .append(h_gate.clone(), [0]) - .unwrap() - .append(cx_gate.clone(), [0, 1]) - .unwrap() - .append(cx_gate.clone(), [1, 0]) - .unwrap(); - - // TODO: Currently left out because we can not represent constants in the model - // let angle = linear.add_constant(ConstF64::new(0.5)); - // linear - // .append_and_consume( - // rz.clone(), - // [CircuitUnit::Linear(0), CircuitUnit::Wire(angle)], - // ) - // .unwrap(); - } - - let outs = linear.finish(); - f_build.finish_with_outputs(outs).unwrap(); - - module_builder.finish_hugr(&FLOAT_OPS_REGISTRY).unwrap() -} - fn bench_builder(c: &mut Criterion) { let mut group = c.benchmark_group("builder"); group.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic)); @@ -187,7 +67,7 @@ fn bench_serialization(c: &mut Criterion) { group.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic)); for size in [0, 1, 10, 100, 1000].iter() { group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| { - let h = circuit(size); + let h = circuit(size).0; b.iter(|| { black_box(roundtrip(&h, JsonSer)); }); @@ -199,7 +79,7 @@ fn bench_serialization(c: &mut Criterion) { group.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic)); for size in [0, 1, 10, 100, 1000].iter() { group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| { - let h = circuit(size); + let h = circuit(size).0; b.iter(|| { black_box(JsonSer.serialize(&h)); }); @@ -213,7 +93,7 @@ fn bench_serialization(c: &mut Criterion) { group.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic)); for size in [0, 1, 10, 100, 1000].iter() { group.bench_with_input(BenchmarkId::from_parameter(size), size, |b, &size| { - let h = circuit(size); + let h = circuit(size).0; b.iter(|| { black_box(roundtrip(&h, CapnpSer)); }); diff --git a/hugr/benches/benchmarks/hugr/examples.rs b/hugr/benches/benchmarks/hugr/examples.rs new file mode 100644 index 000000000..3abcee535 --- /dev/null +++ b/hugr/benches/benchmarks/hugr/examples.rs @@ -0,0 +1,143 @@ +//! Builders and utilities for benchmarks. + +use hugr::builder::{ + BuildError, CFGBuilder, Container, DFGBuilder, Dataflow, DataflowHugr, DataflowSubContainer, + HugrBuilder, ModuleBuilder, +}; +use hugr::extension::prelude::{BOOL_T, QB_T, USIZE_T}; +use hugr::extension::PRELUDE_REGISTRY; +use hugr::ops::OpName; +use hugr::std_extensions::arithmetic::float_ops::FLOAT_OPS_REGISTRY; +use hugr::std_extensions::arithmetic::float_types::FLOAT64_TYPE; +use hugr::types::Signature; +use hugr::{type_row, Extension, Hugr, Node}; +use lazy_static::lazy_static; + +pub fn simple_dfg_hugr() -> Hugr { + let dfg_builder = + DFGBuilder::new(Signature::new(type_row![BOOL_T], type_row![BOOL_T])).unwrap(); + let [i1] = dfg_builder.input_wires_arr(); + dfg_builder.finish_prelude_hugr_with_outputs([i1]).unwrap() +} + +pub fn simple_cfg_builder + AsRef>( + cfg_builder: &mut CFGBuilder, +) -> Result<(), BuildError> { + let sum2_variants = vec![type_row![USIZE_T], type_row![USIZE_T]]; + let mut entry_b = cfg_builder.entry_builder(sum2_variants.clone(), type_row![])?; + let entry = { + let [inw] = entry_b.input_wires_arr(); + + let sum = entry_b.make_sum(1, sum2_variants, [inw])?; + entry_b.finish_with_outputs(sum, [])? + }; + let mut middle_b = cfg_builder + .simple_block_builder(Signature::new(type_row![USIZE_T], type_row![USIZE_T]), 1)?; + let middle = { + let c = middle_b.add_load_const(hugr::ops::Value::unary_unit_sum()); + let [inw] = middle_b.input_wires_arr(); + middle_b.finish_with_outputs(c, [inw])? + }; + let exit = cfg_builder.exit_block(); + cfg_builder.branch(&entry, 0, &middle)?; + cfg_builder.branch(&middle, 0, &exit)?; + cfg_builder.branch(&entry, 1, &exit)?; + Ok(()) +} + +pub fn simple_cfg_hugr() -> Hugr { + let mut cfg_builder = + CFGBuilder::new(Signature::new(type_row![USIZE_T], type_row![USIZE_T])).unwrap(); + simple_cfg_builder(&mut cfg_builder).unwrap(); + cfg_builder.finish_prelude_hugr().unwrap() +} + +lazy_static! { + static ref QUANTUM_EXT: Extension = { + let mut extension = Extension::new( + "bench.quantum".try_into().unwrap(), + hugr::extension::Version::new(0, 0, 0), + ); + + extension + .add_op( + OpName::new_inline("H"), + "".into(), + Signature::new_endo(QB_T), + ) + .unwrap(); + extension + .add_op( + OpName::new_inline("Rz"), + "".into(), + Signature::new(type_row![QB_T, FLOAT64_TYPE], type_row![QB_T]), + ) + .unwrap(); + + extension + .add_op( + OpName::new_inline("CX"), + "".into(), + Signature::new_endo(type_row![QB_T, QB_T]), + ) + .unwrap(); + extension + }; +} + +/// The node ids for a layer generated by the [`circuit`] function. +pub struct CircuitLayer { + pub h: Node, + pub cx1: Node, + pub cx2: Node, +} + +/// Construct a quantum circuit with two qubits and `layers` layers applying `H q0; CX q0, q1; CX q1, q0`. +pub fn circuit(layers: usize) -> (Hugr, Vec) { + let h_gate = QUANTUM_EXT + .instantiate_extension_op("H", [], &PRELUDE_REGISTRY) + .unwrap(); + let cx_gate = QUANTUM_EXT + .instantiate_extension_op("CX", [], &PRELUDE_REGISTRY) + .unwrap(); + // let rz = QUANTUM_EXT + // .instantiate_extension_op("Rz", [], &FLOAT_OPS_REGISTRY) + // .unwrap(); + let signature = + Signature::new_endo(type_row![QB_T, QB_T]).with_extension_delta(QUANTUM_EXT.name().clone()); + let mut module_builder = ModuleBuilder::new(); + let mut f_build = module_builder.define_function("main", signature).unwrap(); + + let wires: Vec<_> = f_build.input_wires().collect(); + + let mut linear = f_build.as_circuit(wires); + + let mut layer_ids = Vec::with_capacity(layers); + for _ in 0..layers { + linear.append(h_gate.clone(), [0]).unwrap(); + let h = linear.tracked_wire(0).unwrap().node(); + linear.append(cx_gate.clone(), [0, 1]).unwrap(); + let cx1 = linear.tracked_wire(0).unwrap().node(); + linear.append(cx_gate.clone(), [1, 0]).unwrap(); + let cx2 = linear.tracked_wire(0).unwrap().node(); + + // TODO: Currently left out because we can not represent constants in the model + // let angle = linear.add_constant(ConstF64::new(0.5)); + // linear + // .append_and_consume( + // rz.clone(), + // [CircuitUnit::Linear(0), CircuitUnit::Wire(angle)], + // ) + // .unwrap(); + + layer_ids.push(CircuitLayer { h, cx1, cx2 }); + } + + let outs = linear.finish(); + f_build.finish_with_outputs(outs).unwrap(); + + ( + module_builder.finish_hugr(&FLOAT_OPS_REGISTRY).unwrap(), + layer_ids, + ) +} diff --git a/hugr/benches/benchmarks/mod.rs b/hugr/benches/benchmarks/mod.rs index a153caa15..bf228a4d7 100644 --- a/hugr/benches/benchmarks/mod.rs +++ b/hugr/benches/benchmarks/mod.rs @@ -1,2 +1,3 @@ pub mod hugr; +pub mod subgraph; pub mod types; diff --git a/hugr/benches/benchmarks/subgraph.rs b/hugr/benches/benchmarks/subgraph.rs new file mode 100644 index 000000000..fbecad6a2 --- /dev/null +++ b/hugr/benches/benchmarks/subgraph.rs @@ -0,0 +1,63 @@ +// Required for black_box uses +#![allow(clippy::unit_arg)] +use hugr::hugr::views::SiblingSubgraph; + +use criterion::{black_box, criterion_group, AxisScale, BenchmarkId, Criterion, PlotConfiguration}; + +use super::hugr::circuit; + +fn bench_singleton_subgraph(c: &mut Criterion) { + let mut group = c.benchmark_group("singleton_subgraph"); + group.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic)); + + let num_layers = [10, 100, 1000]; + + for layers in num_layers.into_iter() { + let (hugr, layer_ids) = circuit(layers); + + // Get a subgraph with a single node. + group.bench_with_input( + BenchmarkId::from_parameter(layers), + &layers, + |b, &layers| { + // Pick a node from the middle of the circuit. + let node = layer_ids.iter().nth(layers / 2).unwrap().cx1; + b.iter(|| black_box(SiblingSubgraph::try_from_nodes([node], &hugr))) + }, + ); + } + + group.finish(); +} + +fn bench_multinode_subgraph(c: &mut Criterion) { + let mut group = c.benchmark_group("multinode_subgraph"); + group.plot_config(PlotConfiguration::default().summary_scale(AxisScale::Logarithmic)); + + let num_layers = [10, 100, 1000]; + + for layers in num_layers.into_iter() { + let (hugr, layer_ids) = circuit(layers); + + // Get a subgraph with a single node. + group.bench_with_input( + BenchmarkId::from_parameter(layers), + &layers, + |b, &_layers| { + // Pick all the hadamard nodes + let nodes: Vec<_> = layer_ids.iter().map(|ids| ids.h).collect(); + b.iter(|| black_box(SiblingSubgraph::try_from_nodes(nodes.clone(), &hugr))) + }, + ); + } + + group.finish(); +} + +criterion_group! { + name = benches; + config = Criterion::default(); + targets = + bench_singleton_subgraph, + bench_multinode_subgraph, +} From 9c77f76aae00f1bf09798e43adbbde80e9c6182d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20Borgna?= Date: Wed, 13 Nov 2024 15:11:53 +0000 Subject: [PATCH 2/4] Set `bench = false` for all the other crates --- hugr-cli/Cargo.toml | 4 ++++ hugr-core/Cargo.toml | 3 +++ hugr-model/Cargo.toml | 3 +++ hugr-passes/Cargo.toml | 3 +++ 4 files changed, 13 insertions(+) diff --git a/hugr-cli/Cargo.toml b/hugr-cli/Cargo.toml index 34e01dca2..362d3efa6 100644 --- a/hugr-cli/Cargo.toml +++ b/hugr-cli/Cargo.toml @@ -12,6 +12,9 @@ description = "Compiler passes for Quantinuum's HUGR" keywords = ["Quantum", "Quantinuum"] categories = ["compilers"] +[lib] +bench = false + [dependencies] clap = { workspace = true, features = ["derive"] } clap-verbosity-flag.workspace = true @@ -42,3 +45,4 @@ rstest.workspace = true name = "hugr" path = "src/main.rs" doc = false +bench = false diff --git a/hugr-core/Cargo.toml b/hugr-core/Cargo.toml index 274c21837..3dab5e709 100644 --- a/hugr-core/Cargo.toml +++ b/hugr-core/Cargo.toml @@ -21,6 +21,9 @@ extension_inference = [] declarative = ["serde_yaml"] model_unstable = ["hugr-model"] +[lib] +bench = false + [[test]] name = "model" required-features = ["model_unstable"] diff --git a/hugr-model/Cargo.toml b/hugr-model/Cargo.toml index 0c24cbe8e..d529e7941 100644 --- a/hugr-model/Cargo.toml +++ b/hugr-model/Cargo.toml @@ -12,6 +12,9 @@ homepage.workspace = true repository.workspace = true license.workspace = true +[lib] +bench = false + [dependencies] bumpalo = { workspace = true, features = ["collections"] } capnp = "0.20.1" diff --git a/hugr-passes/Cargo.toml b/hugr-passes/Cargo.toml index 3d7f0fc90..24a674051 100644 --- a/hugr-passes/Cargo.toml +++ b/hugr-passes/Cargo.toml @@ -12,6 +12,9 @@ description = "Compiler passes for Quantinuum's HUGR" keywords = ["Quantum", "Quantinuum"] categories = ["compilers"] +[lib] +bench = false + [dependencies] hugr-core = { path = "../hugr-core", version = "0.13.3" } itertools = { workspace = true } From 22f3a72fb78b0d3d0bd97a6d6a59287217a9d566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20Borgna?= Date: Wed, 13 Nov 2024 15:21:01 +0000 Subject: [PATCH 3/4] perf: Avoid initializing the topo checker unless needed --- hugr-core/src/hugr/views/sibling_subgraph.rs | 28 +++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/hugr-core/src/hugr/views/sibling_subgraph.rs b/hugr-core/src/hugr/views/sibling_subgraph.rs index dc79979ec..167d76419 100644 --- a/hugr-core/src/hugr/views/sibling_subgraph.rs +++ b/hugr-core/src/hugr/views/sibling_subgraph.rs @@ -9,6 +9,7 @@ //! while the former provide views for subgraphs within a single level of the //! hierarchy. +use std::cell::OnceCell; use std::collections::HashSet; use std::mem; @@ -453,15 +454,24 @@ fn combine_in_out<'a>( /// /// This can be used when constructing multiple sibling subgraphs to speed up /// convexity checking. -pub struct TopoConvexChecker<'g, Base: 'g + HugrView>( - portgraph::algorithms::TopoConvexChecker>, -); +pub struct TopoConvexChecker<'g, Base: 'g + HugrView> { + base: &'g Base, + checker: OnceCell>>, +} impl<'g, Base: HugrView> TopoConvexChecker<'g, Base> { /// Create a new convexity checker. pub fn new(base: &'g Base) -> Self { - let pg = base.portgraph(); - Self(portgraph::algorithms::TopoConvexChecker::new(pg)) + Self { + base, + checker: OnceCell::new(), + } + } + + /// Returns the portgraph convexity checker, initializing it if necessary. + fn get_checker(&self) -> &portgraph::algorithms::TopoConvexChecker> { + self.checker + .get_or_init(|| portgraph::algorithms::TopoConvexChecker::new(self.base.portgraph())) } } @@ -472,7 +482,13 @@ impl<'g, Base: HugrView> ConvexChecker for TopoConvexChecker<'g, Base> { inputs: impl IntoIterator, outputs: impl IntoIterator, ) -> bool { - self.0.is_convex(nodes, inputs, outputs) + let mut nodes = nodes.into_iter().multipeek(); + // If the node iterator contains less than two nodes, the subgraph is + // trivially convex. + if nodes.peek().is_none() || nodes.peek().is_none() { + return true; + }; + self.get_checker().is_convex(nodes, inputs, outputs) } } From 1c993b46d97d3b8781596d194e0f327f13a8bfcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20Borgna?= Date: Wed, 13 Nov 2024 15:46:06 +0000 Subject: [PATCH 4/4] Fix clippy --- .pre-commit-config.yaml | 2 +- hugr/benches/benchmarks/subgraph.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 31b5e9328..79e82cad2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -81,7 +81,7 @@ repos: - id: cargo-clippy name: cargo clippy description: Run clippy lints with `cargo clippy`. - entry: cargo clippy --all-features --workspace -- -D warnings + entry: cargo clippy --all-targets --all-features --workspace -- -D warnings language: system files: \.rs$ pass_filenames: false diff --git a/hugr/benches/benchmarks/subgraph.rs b/hugr/benches/benchmarks/subgraph.rs index fbecad6a2..58ff24cdd 100644 --- a/hugr/benches/benchmarks/subgraph.rs +++ b/hugr/benches/benchmarks/subgraph.rs @@ -21,7 +21,7 @@ fn bench_singleton_subgraph(c: &mut Criterion) { &layers, |b, &layers| { // Pick a node from the middle of the circuit. - let node = layer_ids.iter().nth(layers / 2).unwrap().cx1; + let node = layer_ids[layers / 2].cx1; b.iter(|| black_box(SiblingSubgraph::try_from_nodes([node], &hugr))) }, );