Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance of barrier() #11345

Merged
merged 7 commits into from
Jan 15, 2024
Merged

Conversation

eendebakpt
Copy link
Contributor

@eendebakpt eendebakpt commented Nov 30, 2023

Summary

Improve performance of barrier() by using the _append when possible.

Details and comments

Benchmark:

import time
from qiskit.circuit import QuantumCircuit
def g():
    qc=QuantumCircuit(2)
    qc.measure_all()

x=[]
for ii in range(20):
    t0=time.time()
    for ii in range(1000):
        g()
    dt=time.time()-t0
    #print(dt)
    x.append(dt)
    
print(f'total: {sum(x):.2f} seconds')

Results:

main: total: 2.08 seconds
PR: total: 1.72 seconds

This is an alternative to #10949 @jakelishman

@eendebakpt eendebakpt requested a review from a team as a code owner November 30, 2023 11:38
@coveralls
Copy link

coveralls commented Nov 30, 2023

Pull Request Test Coverage Report for Build 7530882971

Warning: This coverage report may be inaccurate.

We've detected an issue with your CI configuration that might affect the accuracy of this pull request's coverage report.
To ensure accuracy in future PRs, please see these guidelines.
A quick fix for this PR: rebase it; your next report should be accurate.

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.02%) to 89.36%

Totals Coverage Status
Change from base Build 7508541254: 0.02%
Covered Lines: 59671
Relevant Lines: 66776

💛 - Coveralls

Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, this has similar problems surrounding the possibility of control flow as #10949 originally had. I think what you're after looks something a bit more like this (warning: untested):

qubits = tuple({q: None for ...} if qargs else self.qubits)
return self._current_scope().append(CircuitInstruction(Barrier(len(qubits)), qubits, ())

That correctly handles control-flow using the new scope API - self._current_scope() returns an implementer of the internal CircuitScopeInterface that abstracts the _append operation (amongst others).

(btw: you don't need to press "update branch"; we automatically bring branches up-to-date when they join the merge queue)

@eendebakpt
Copy link
Contributor Author

@jakelishman Thanks for the pointer. How could I write a test for the control flow case? And what to test for exactly? I tried this, and it runs without errors.

from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.circuit import IfElseOp

true_body = QuantumCircuit(2)
true_body.x(1)
true_body.barrier() # barrier inside conditional

qc = QuantumCircuit(2,2)
qc.h(0)
qc.measure(0, 0)

op = IfElseOp((qc.clbits[0], True), true_body, None)
qc.append(op, qargs=[0, 1], cargs=[])

qc.draw()

@jakelishman
Copy link
Member

Right - QuantumCircuit.append (no underscore) respects control-flow builder scopes already, but QuantumCircuit._append is the internal worker of the QuantumCircuit innermost scope, so calling it (deliberately) skips any active builder scope. For example, using commit 35ec9ee as the current head of this PR:

from qiskit import QuantumCircuit

qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])
with qc.if_test((qc.clbits[0], False)):
    qc.h(0)
    qc.barrier()
    qc.h(0)
    qc.barrier()
    qc.h(0)
qc.draw()
     ┌───┐     ┌─┐    ░  ░   ┌──────  ┌───┐┌───┐┌───┐ ───────┐
q_0: ┤ H ├──■──┤M├────░──░───┤ If-0  ─┤ H ├┤ H ├┤ H ├  End-0 ├─
     └───┘┌─┴─┐└╥┘┌─┐ ░  ░   └──╥───  └───┘└───┘└───┘ ───────┘
q_1: ─────┤ X ├─╫─┤M├─░──░──────╫──────────────────────────────
          └───┘ ║ └╥┘ ░  ░ ┌────╨────┐
c: 2/═══════════╩══╩═══════╡ c_0=0x0 ╞═════════════════════════
                0  1       └─────────┘

where the barriers have migrated outside the "if". The result should look like if I'd manually constructed all the scopes explicitly:

from qiskit import QuantumCircuit

qc = QuantumCircuit(2, 2)
qc.h(0)
qc.cx(0, 1)
qc.measure([0, 1], [0, 1])

true_body = QuantumCircuit(qc.qubits, [qc.clbits[0]])
true_body.h(0)
true_body.barrier()
true_body.h(0)
true_body.barrier()
true_body.h(0)

qc.if_test((qc.clbits[0], False), true_body, qc.qubits, [qc.clbits[0]])
qc.draw()
     ┌───┐     ┌─┐     ┌──────  ┌───┐ ░ ┌───┐ ░ ┌───┐ ───────┐
q_0: ┤ H ├──■──┤M├─────┤       ─┤ H ├─░─┤ H ├─░─┤ H ├        ├─
     └───┘┌─┴─┐└╥┘┌─┐  │ If-0   └───┘ ░ └───┘ ░ └───┘  End-0 │
q_1: ─────┤ X ├─╫─┤M├──┤       ───────░───────░──────        ├─
          └───┘ ║ └╥┘  └──╥───        ░       ░       ───────┘
                ║  ║ ┌────╨────┐
c: 2/═══════════╩══╩═╡ c_0=0x0 ╞═══════════════════════════════
                0  1 └─────────┘

@eendebakpt
Copy link
Contributor Author

Updated benchmark:

def g():
    qc = QuantumCircuit(2, 2)
    for ii in range(5):
        qc.barrier(qc.qubits)

Output of %timeit g() for main and this PR:

main: 259 µs ± 18.7 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
pr:149 µs ± 8.41 µs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

qubits = tuple({q: None for qarg in qargs for q in self.qbit_argument_conversion(qarg)})
return self.append(CircuitInstruction(Barrier(len(qubits), label=label), qubits, ()))
else:
qubits = self.qubits.copy()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copy might not be necessary, but it does not hurt performance so I left it as it was originally coded.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine either way - self.qubits is a semi-dangerous object because it's a direct view onto QuantumCircuit internals and so mustn't be mutated, but the construction of a CircuitInstruction will consume it into a tuple anyway, so it wouldn't matter. Since it's irrelevant to performance, it's fine to just leave it.

qubits = tuple({q: None for qarg in qargs for q in self.qbit_argument_conversion(qarg)})
return self.append(CircuitInstruction(Barrier(len(qubits), label=label), qubits, ()))
else:
qubits = self.qubits.copy()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine either way - self.qubits is a semi-dangerous object because it's a direct view onto QuantumCircuit internals and so mustn't be mutated, but the construction of a CircuitInstruction will consume it into a tuple anyway, so it wouldn't matter. Since it's irrelevant to performance, it's fine to just leave it.

Copy link
Member

@jakelishman jakelishman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for sticking with this!

@jakelishman jakelishman added performance Changelog: None Do not include in changelog mod: circuit Related to the core of the `QuantumCircuit` class or the circuit library labels Jan 15, 2024
@jakelishman jakelishman added this to the 1.0.0 milestone Jan 15, 2024
@jakelishman jakelishman enabled auto-merge January 15, 2024 15:20
@jakelishman jakelishman added this pull request to the merge queue Jan 15, 2024
Merged via the queue into Qiskit:main with commit 4aff15a Jan 15, 2024
12 checks passed
@eendebakpt eendebakpt deleted the barrier_optimize branch January 16, 2024 21:04
ShellyGarion pushed a commit to ShellyGarion/qiskit-terra that referenced this pull request Jan 18, 2024
* Improve performance of barrier()

* black

* fix barrier statement in context

* add test

* black

* fix label argument

* Use `unittest` test method

---------

Co-authored-by: Jake Lishman <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: None Do not include in changelog mod: circuit Related to the core of the `QuantumCircuit` class or the circuit library performance
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants