Skip to content

Commit 01642a8

Browse files
authored
Fix performance regression in UnitarySynthesis (#13375)
* Fix performance regression in UnitarySynthesis This commit fixes a performance regression that was introduced in PR #13141. When the pass is looking up the preferred synthesis direction for a unitary based on the connectvity constraints the connectivity was being provided as a PyList. To look up the edge in connectivity set this meant we needed to iterate over the list and then create a set that rust could lookup if it contains an edge or it's reverse. This has significant overhead because its iterating via python and also iterating per decomposition. This commit addresses this by changing the input type to be a HashSet from Python so Pyo3 will convert a pyset directly to a HashSet once at call time and that's used by reference for lookups directly instead of needing to iterate over the list each time. * Avoid constructing plugin data views with default plugin If we're using the default plugin the execution will happen in rust and we don't need to build the plugin data views that are defined in the plugin interface. Profiling the benchpress test using the hamlib hamiltonian: ham_graph-2D-grid-nonpbc-qubitnodes_Lx-5_Ly-186_h-0.5-all-to-all that caught this originally regression was showing an inordinate amount of time being spent in the construction of `_build_gate_lengths_by_qubit` and `_build_gate_errors_by_qubit` which isn't being used because this happens internally in rust now. To mitigate this overhead this commit migrates the sections computing these values to the code branch that uses them and not the default plugin branch that uses rust.
1 parent e7fc5a7 commit 01642a8

File tree

2 files changed

+45
-49
lines changed

2 files changed

+45
-49
lines changed

crates/accelerate/src/unitary_synthesis.rs

+8-14
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use smallvec::{smallvec, SmallVec};
2727

2828
use pyo3::intern;
2929
use pyo3::prelude::*;
30-
use pyo3::types::{IntoPyDict, PyDict, PyList, PyString};
30+
use pyo3::types::{IntoPyDict, PyDict, PyString};
3131
use pyo3::wrap_pyfunction;
3232
use pyo3::Python;
3333

@@ -225,7 +225,7 @@ fn py_run_main_loop(
225225
qubit_indices: Vec<usize>,
226226
min_qubits: usize,
227227
target: &Target,
228-
coupling_edges: &Bound<'_, PyList>,
228+
coupling_edges: HashSet<[PhysicalQubit; 2]>,
229229
approximation_degree: Option<f64>,
230230
natural_direction: Option<bool>,
231231
) -> PyResult<DAGCircuit> {
@@ -268,7 +268,7 @@ fn py_run_main_loop(
268268
new_ids,
269269
min_qubits,
270270
target,
271-
coupling_edges,
271+
coupling_edges.clone(),
272272
approximation_degree,
273273
natural_direction,
274274
)?;
@@ -352,7 +352,7 @@ fn py_run_main_loop(
352352
py,
353353
unitary,
354354
ref_qubits,
355-
coupling_edges,
355+
&coupling_edges,
356356
target,
357357
approximation_degree,
358358
natural_direction,
@@ -383,7 +383,7 @@ fn run_2q_unitary_synthesis(
383383
py: Python,
384384
unitary: Array2<Complex64>,
385385
ref_qubits: &[PhysicalQubit; 2],
386-
coupling_edges: &Bound<'_, PyList>,
386+
coupling_edges: &HashSet<[PhysicalQubit; 2]>,
387387
target: &Target,
388388
approximation_degree: Option<f64>,
389389
natural_direction: Option<bool>,
@@ -794,7 +794,7 @@ fn preferred_direction(
794794
decomposer: &DecomposerElement,
795795
ref_qubits: &[PhysicalQubit; 2],
796796
natural_direction: Option<bool>,
797-
coupling_edges: &Bound<'_, PyList>,
797+
coupling_edges: &HashSet<[PhysicalQubit; 2]>,
798798
target: &Target,
799799
) -> PyResult<Option<bool>> {
800800
// Returns:
@@ -830,14 +830,8 @@ fn preferred_direction(
830830
Some(false) => None,
831831
_ => {
832832
// None or Some(true)
833-
let mut edge_set = HashSet::new();
834-
for item in coupling_edges.iter() {
835-
if let Ok(tuple) = item.extract::<(usize, usize)>() {
836-
edge_set.insert(tuple);
837-
}
838-
}
839-
let zero_one = edge_set.contains(&(qubits[0].0 as usize, qubits[1].0 as usize));
840-
let one_zero = edge_set.contains(&(qubits[1].0 as usize, qubits[0].0 as usize));
833+
let zero_one = coupling_edges.contains(&qubits);
834+
let one_zero = coupling_edges.contains(&[qubits[1], qubits[0]]);
841835

842836
match (zero_one, one_zero) {
843837
(true, false) => Some(true),

qiskit/transpiler/passes/synthesis/unitary_synthesis.py

+37-35
Original file line numberDiff line numberDiff line change
@@ -457,37 +457,6 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:
457457
default_kwargs = {}
458458
method_list = [(plugin_method, plugin_kwargs), (default_method, default_kwargs)]
459459

460-
for method, kwargs in method_list:
461-
if method.supports_basis_gates:
462-
kwargs["basis_gates"] = self._basis_gates
463-
if method.supports_natural_direction:
464-
kwargs["natural_direction"] = self._natural_direction
465-
if method.supports_pulse_optimize:
466-
kwargs["pulse_optimize"] = self._pulse_optimize
467-
if method.supports_gate_lengths:
468-
_gate_lengths = _gate_lengths or _build_gate_lengths(
469-
self._backend_props, self._target
470-
)
471-
kwargs["gate_lengths"] = _gate_lengths
472-
if method.supports_gate_errors:
473-
_gate_errors = _gate_errors or _build_gate_errors(self._backend_props, self._target)
474-
kwargs["gate_errors"] = _gate_errors
475-
if method.supports_gate_lengths_by_qubit:
476-
_gate_lengths_by_qubit = _gate_lengths_by_qubit or _build_gate_lengths_by_qubit(
477-
self._backend_props, self._target
478-
)
479-
kwargs["gate_lengths_by_qubit"] = _gate_lengths_by_qubit
480-
if method.supports_gate_errors_by_qubit:
481-
_gate_errors_by_qubit = _gate_errors_by_qubit or _build_gate_errors_by_qubit(
482-
self._backend_props, self._target
483-
)
484-
kwargs["gate_errors_by_qubit"] = _gate_errors_by_qubit
485-
supported_bases = method.supported_bases
486-
if supported_bases is not None:
487-
kwargs["matched_basis"] = _choose_bases(self._basis_gates, supported_bases)
488-
if method.supports_target:
489-
kwargs["target"] = self._target
490-
491460
# Handle approximation degree as a special case for backwards compatibility, it's
492461
# not part of the plugin interface and only something needed for the default
493462
# pass.
@@ -503,22 +472,55 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:
503472
else {}
504473
)
505474

506-
if self.method == "default" and isinstance(kwargs["target"], Target):
475+
if self.method == "default" and self._target is not None:
507476
_coupling_edges = (
508-
list(self._coupling_map.get_edges()) if self._coupling_map is not None else []
477+
set(self._coupling_map.get_edges()) if self._coupling_map is not None else set()
509478
)
510479

511480
out = run_default_main_loop(
512481
dag,
513482
list(qubit_indices.values()),
514483
self._min_qubits,
515-
kwargs["target"],
484+
self._target,
516485
_coupling_edges,
517486
self._approximation_degree,
518-
kwargs["natural_direction"],
487+
self._natural_direction,
519488
)
520489
return out
521490
else:
491+
for method, kwargs in method_list:
492+
if method.supports_basis_gates:
493+
kwargs["basis_gates"] = self._basis_gates
494+
if method.supports_natural_direction:
495+
kwargs["natural_direction"] = self._natural_direction
496+
if method.supports_pulse_optimize:
497+
kwargs["pulse_optimize"] = self._pulse_optimize
498+
if method.supports_gate_lengths:
499+
_gate_lengths = _gate_lengths or _build_gate_lengths(
500+
self._backend_props, self._target
501+
)
502+
kwargs["gate_lengths"] = _gate_lengths
503+
if method.supports_gate_errors:
504+
_gate_errors = _gate_errors or _build_gate_errors(
505+
self._backend_props, self._target
506+
)
507+
kwargs["gate_errors"] = _gate_errors
508+
if method.supports_gate_lengths_by_qubit:
509+
_gate_lengths_by_qubit = _gate_lengths_by_qubit or _build_gate_lengths_by_qubit(
510+
self._backend_props, self._target
511+
)
512+
kwargs["gate_lengths_by_qubit"] = _gate_lengths_by_qubit
513+
if method.supports_gate_errors_by_qubit:
514+
_gate_errors_by_qubit = _gate_errors_by_qubit or _build_gate_errors_by_qubit(
515+
self._backend_props, self._target
516+
)
517+
kwargs["gate_errors_by_qubit"] = _gate_errors_by_qubit
518+
supported_bases = method.supported_bases
519+
if supported_bases is not None:
520+
kwargs["matched_basis"] = _choose_bases(self._basis_gates, supported_bases)
521+
if method.supports_target:
522+
kwargs["target"] = self._target
523+
522524
out = self._run_main_loop(
523525
dag, qubit_indices, plugin_method, plugin_kwargs, default_method, default_kwargs
524526
)

0 commit comments

Comments
 (0)