Repository: Qiskit/qiskit

Issue:
CalibrationBuilder scales superlinearly
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

### What is the expected enhancement?
For a random circuit of equal width and depth the CalibrationBuilder pass scales like n_qubits^3 whereas one might expect scaling like n_qubits^2 if it was proportional to the number of gates. This slow down seems to be in retrieving the qubits associated with a dag node. 

This test was run without actual calibrations so only part of the pass code is actually profiled.

![image](https://user-images.githubusercontent.com/25756217/138739153-9509e251-e9f2-4e5a-ba7f-86e82ff520a3.png)

![image](https://user-images.githubusercontent.com/25756217/138739217-1640bd12-8234-413e-8d44-8eecded65119.png)

This is qiskit-terra version 0.19.0.dev0+637acc0.




Discussion:
The obvious culprit I assume are these lines:
```python
        for node in dag.gate_nodes():
            qubits = list(dag.qubits.index(q) for q in node.qargs)
```

That looks pretty cubic to me - "for op in circuit: for qubit in op: for bit in dag: if qubit == bit: ...".  I don't know off the top of my head if `DAGCircuit` gained the benefits of #6621 - I assume probably not, because keeping it updated would be a bit gross.  The simplest solution is probably just to do a little bit of
```python
qubit_map = {q: i for i, q in enumerate(dag.qubits)}
for node in dag.gate_nodes():
    qubits = [qubit_map[q] for q in node.qargs]
```
in the transpiler pass manually, which should drop it back down to quadratic complexity.

There's also an easy win in `Bit.__eq__`:
```python
def __eq__(self, other):
    if (self._register, self._index) == (None, None):
        return self is other
    # ... else handle old-style bits ...
```
The `if` check is a bit inefficient here: `if self._register is None and self._index is None` should be faster, because it avoids creation of temporary tuples, and avoids a call to `Register.__eq__`.


Yeah, it was a follow up for #6621 to add an equivalent method for DAGCircuit https://github.com/Qiskit/qiskit-terra/pull/6621#issuecomment-885934083 just adding the dict comprehension to get the indices is the best way to do this. That's what we've been doing in other transpiler passes while we wait for an equivalent to #6621 for DAGCircuit.

Resolving patch:
diff --git a/qiskit/transpiler/passes/calibration/builders.py b/qiskit/transpiler/passes/calibration/builders.py
--- a/qiskit/transpiler/passes/calibration/builders.py
+++ b/qiskit/transpiler/passes/calibration/builders.py
@@ -74,8 +74,9 @@ def run(self, dag: DAGCircuit) -> DAGCircuit:
         Returns:
             A DAG with calibrations added to it.
         """
+        qubit_map = {qubit: i for i, qubit in enumerate(dag.qubits)}
         for node in dag.gate_nodes():
-            qubits = list(dag.qubits.index(q) for q in node.qargs)
+            qubits = [qubit_map[q] for q in node.qargs]
 
             if self.supported(node.op, qubits) and not dag.has_calibration_for(node):
                 # calibration can be provided and no user-defined calibration is already provided


Test patch: