Repository: Qiskit/qiskit

Issue:
Performance Regression Caused by Introduction of Bit Classes
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues -->

### Information

- **Qiskit Terra version**: Master
- **Python version**: Any
- **Operating system**: Any

### What is the current behavior?

When #2414 was introduced it came with a sizeable performance regression in transpilation. This is best seen here: http://169.61.88.206/#quantum_volume.QuantumVolumeBenchmark.time_ibmq_backend_transpile?p-width=14&p-depth=34&commits=79c44e56

It is typically between ~1.2X  and ~1.9X times slower to transpile circuits with #2414 applied.

As a follow-on to: https://github.com/Qiskit/qiskit-terra/pull/2499#issuecomment-498268328 there is work to improve the performance introduced by #2414 in progress with #2529 and #2525, but it's not clear from those whether it will return to the prior performance or not. This issue is to track the progress to make sure we fix this prior to the next release.

### Suggested solutions

Fix the performance regression.


Discussion:
@kdk added some additional benchmarks for circuit creation and the bit classes introduced an even larger performance regression there. The worst case was 5.87x slower: https://qiskit.github.io/qiskit/#circuit_construction.CircuitConstructionBench.time_circuit_extend?p-width=20&p-gates=2048&commits=79c44e56 

Resolving patch:
diff --git a/qiskit/circuit/register.py b/qiskit/circuit/register.py
--- a/qiskit/circuit/register.py
+++ b/qiskit/circuit/register.py
@@ -62,6 +62,8 @@ def __init__(self, size, name=None):
         self.name = name
         self.size = size
 
+        self._bits = [self.bit_type(self, idx) for idx in range(size)]
+
     def __repr__(self):
         """Return the official string representing the register."""
         return "%s(%d, '%s')" % (self.__class__.__qualname__, self.size, self.name)
@@ -87,14 +89,14 @@ def __getitem__(self, key):
         if not isinstance(key, (int, slice, list)):
             raise QiskitError("expected integer or slice index into register")
         if isinstance(key, slice):
-            return [self.bit_type(self, ind) for ind in range(*key.indices(len(self)))]
+            return self._bits[key]
         elif isinstance(key, list):  # list of qubit indices
             if max(key) < len(self):
-                return [self.bit_type(self, ind) for ind in key]
+                return [self._bits[idx] for idx in key]
             else:
                 raise QiskitError('register index out of range')
         else:
-            return self.bit_type(self, key)
+            return self._bits[key]
 
     def __iter__(self):
         for bit in range(self.size):


Test patch:
diff --git a/test/python/circuit/test_circuit_registers.py b/test/python/circuit/test_circuit_registers.py
--- a/test/python/circuit/test_circuit_registers.py
+++ b/test/python/circuit/test_circuit_registers.py
@@ -93,6 +93,7 @@ def test_negative_index(self):
         self.assertEqual(qr1[-1], qr1[9])
         self.assertEqual(qr1[-3:-1], [qr1[7], qr1[8]])
         self.assertEqual(len(cr1[0:-2]), 8)
+        self.assertEqual(qr1[[-1, -3, -5]], [qr1[9], qr1[7], qr1[5]])
 
     def test_reg_equal(self):
         """Test getting quantum registers from circuit.
diff --git a/test/python/pickles/TestsBasicSwap_a_cx_to_map.pickle b/test/python/pickles/TestsBasicSwap_a_cx_to_map.pickle
Binary files a/test/python/pickles/TestsBasicSwap_a_cx_to_map.pickle and b/test/python/pickles/TestsBasicSwap_a_cx_to_map.pickle differ
diff --git a/test/python/pickles/TestsBasicSwap_handle_measurement.pickle b/test/python/pickles/TestsBasicSwap_handle_measurement.pickle
Binary files a/test/python/pickles/TestsBasicSwap_handle_measurement.pickle and b/test/python/pickles/TestsBasicSwap_handle_measurement.pickle differ
diff --git a/test/python/pickles/TestsBasicSwap_initial_layout.pickle b/test/python/pickles/TestsBasicSwap_initial_layout.pickle
Binary files a/test/python/pickles/TestsBasicSwap_initial_layout.pickle and b/test/python/pickles/TestsBasicSwap_initial_layout.pickle differ
diff --git a/test/python/pickles/TestsLookaheadSwap_a_cx_to_map.pickle b/test/python/pickles/TestsLookaheadSwap_a_cx_to_map.pickle
Binary files a/test/python/pickles/TestsLookaheadSwap_a_cx_to_map.pickle and b/test/python/pickles/TestsLookaheadSwap_a_cx_to_map.pickle differ
diff --git a/test/python/pickles/TestsLookaheadSwap_handle_measurement.pickle b/test/python/pickles/TestsLookaheadSwap_handle_measurement.pickle
Binary files a/test/python/pickles/TestsLookaheadSwap_handle_measurement.pickle and b/test/python/pickles/TestsLookaheadSwap_handle_measurement.pickle differ
diff --git a/test/python/pickles/TestsLookaheadSwap_initial_layout.pickle b/test/python/pickles/TestsLookaheadSwap_initial_layout.pickle
Binary files a/test/python/pickles/TestsLookaheadSwap_initial_layout.pickle and b/test/python/pickles/TestsLookaheadSwap_initial_layout.pickle differ
diff --git a/test/python/pickles/TestsStochasticSwap_a_cx_to_map.pickle b/test/python/pickles/TestsStochasticSwap_a_cx_to_map.pickle
Binary files a/test/python/pickles/TestsStochasticSwap_a_cx_to_map.pickle and b/test/python/pickles/TestsStochasticSwap_a_cx_to_map.pickle differ
diff --git a/test/python/pickles/TestsStochasticSwap_handle_measurement.pickle b/test/python/pickles/TestsStochasticSwap_handle_measurement.pickle
Binary files a/test/python/pickles/TestsStochasticSwap_handle_measurement.pickle and b/test/python/pickles/TestsStochasticSwap_handle_measurement.pickle differ
diff --git a/test/python/pickles/TestsStochasticSwap_initial_layout.pickle b/test/python/pickles/TestsStochasticSwap_initial_layout.pickle
Binary files a/test/python/pickles/TestsStochasticSwap_initial_layout.pickle and b/test/python/pickles/TestsStochasticSwap_initial_layout.pickle differ