Skip to content

Commit d3b8b91

Browse files
authored
Avoid ExtraInstructionAttributes allocation on unit="dt" (#13078)
The default value for `Instruction.unit` is `"dt"`. Previously, the `OperationFromPython` extraction logic would only suppress allocation of the extra instruction attributes if all the contained fields were `None`, but `None` is not actually a valid value of `Instruction.unit` (which must be a string). This meant that `OperationFromPython` would always allocate and store extra attributes, even for the default cases. This did not affect standard gates appended using their corresponding `QuantumCircuit` methods (since no Python-space extraction is performed in that case), but did affect standard calls to `append`, or anything else that entered from Python space. This drastically reduces the memory usage of circuits built by `append`-like methods. Ignoring the inefficiency factor of the heap-allocation implementation, this saves 66 bytes plus small-allocation overhead for 2-byte heap allocations (another 14 bytes on macOS, but will vary depending on the allocator) per standard instruction, which is on the order of 40% memory-usage reduction.
1 parent dde09f9 commit d3b8b91

File tree

2 files changed

+34
-5
lines changed

2 files changed

+34
-5
lines changed

crates/circuit/src/circuit_instruction.rs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use numpy::IntoPyArray;
1717
use pyo3::basic::CompareOp;
1818
use pyo3::exceptions::{PyDeprecationWarning, PyTypeError};
1919
use pyo3::prelude::*;
20-
use pyo3::types::{PyList, PyTuple, PyType};
20+
use pyo3::types::{PyList, PyString, PyTuple, PyType};
2121
use pyo3::{intern, IntoPy, PyObject, PyResult};
2222

2323
use smallvec::SmallVec;
@@ -63,6 +63,20 @@ impl ExtraInstructionAttributes {
6363
None
6464
}
6565
}
66+
67+
/// Get the Python-space version of the stored `unit`. This evalutes the Python-space default
68+
/// (`"dt"`) value if we're storing a `None`.
69+
pub fn py_unit(&self, py: Python) -> Py<PyString> {
70+
self.unit
71+
.as_deref()
72+
.map(|unit| <&str as IntoPy<Py<PyString>>>::into_py(unit, py))
73+
.unwrap_or_else(|| Self::default_unit(py).clone().unbind())
74+
}
75+
76+
/// Get the Python-space default value for the `unit` field.
77+
pub fn default_unit(py: Python) -> &Bound<PyString> {
78+
intern!(py, "dt")
79+
}
6680
}
6781

6882
/// A single instruction in a :class:`.QuantumCircuit`, comprised of the :attr:`operation` and
@@ -267,10 +281,17 @@ impl CircuitInstruction {
267281
}
268282

269283
#[getter]
270-
fn unit(&self) -> Option<&str> {
284+
fn unit(&self, py: Python) -> Py<PyString> {
285+
// Python space uses `"dt"` as the default, whereas we simply don't store the extra
286+
// attributes at all if they're none.
271287
self.extra_attrs
272288
.as_ref()
273-
.and_then(|attrs| attrs.unit.as_deref())
289+
.map(|attrs| attrs.py_unit(py))
290+
.unwrap_or_else(|| {
291+
ExtraInstructionAttributes::default_unit(py)
292+
.clone()
293+
.unbind()
294+
})
274295
}
275296

276297
/// Is the :class:`.Operation` contained in this instruction a Qiskit standard gate?
@@ -524,10 +545,18 @@ impl<'py> FromPyObject<'py> for OperationFromPython {
524545
.map(|params| params.unwrap_or_default())
525546
};
526547
let extract_extra = || -> PyResult<_> {
548+
let unit = {
549+
// We accept Python-space `None` or `"dt"` as both meaning the default `"dt"`.
550+
let raw_unit = ob.getattr(intern!(py, "unit"))?;
551+
(!(raw_unit.is_none()
552+
|| raw_unit.eq(ExtraInstructionAttributes::default_unit(py))?))
553+
.then(|| raw_unit.extract::<String>())
554+
.transpose()?
555+
};
527556
Ok(ExtraInstructionAttributes::new(
528557
ob.getattr(intern!(py, "label"))?.extract()?,
529558
ob.getattr(intern!(py, "duration"))?.extract()?,
530-
ob.getattr(intern!(py, "unit"))?.extract()?,
559+
unit,
531560
ob.getattr(intern!(py, "condition"))?.extract()?,
532561
)
533562
.map(Box::from))

crates/circuit/src/operations.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,7 @@ impl StandardGate {
435435
if let Some(extra) = extra_attrs {
436436
let kwargs = [
437437
("label", extra.label.to_object(py)),
438-
("unit", extra.unit.to_object(py)),
438+
("unit", extra.py_unit(py).into_any()),
439439
("duration", extra.duration.to_object(py)),
440440
]
441441
.into_py_dict_bound(py);

0 commit comments

Comments
 (0)