Repository: Qiskit/qiskit

Issue:
Deepcopy usage causes 3x performance regression
<!-- ⚠️ 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?

In #1816 we redefined the instruction class so it didn't carry the qargs and cargs internally. However, this change broke how we were doing dag->circuit and circuit->dag in `qiskit.converters.dag_to_circuit()` and `qiskit.converters.circuit_to_dag()` respectively. In that PR the previous method which just created a duplicate object for the input instruction was removed and replaced by a call to copy.deepcopy(). This ends up significantly hurting performance in certain cases and continuous benchmarking is showing a 3x performance regression on simulator transpiler benchmarks in: https://github.com/Qiskit/qiskit/tree/master/test/benchmarks

### Steps to reproduce the problem


### What is the expected behavior?


### Suggested solutions

Remove the deep copy usage



Discussion:

Resolving patch:
diff --git a/qiskit/converters/circuit_to_dag.py b/qiskit/converters/circuit_to_dag.py
--- a/qiskit/converters/circuit_to_dag.py
+++ b/qiskit/converters/circuit_to_dag.py
@@ -7,8 +7,8 @@
 
 """Helper function for converting a circuit to a dag"""
 
-import copy
-
+from qiskit.circuit import gate
+from qiskit.circuit import instruction as inst_mod
 from qiskit.dagcircuit.dagcircuit import DAGCircuit
 
 
@@ -35,7 +35,37 @@ def circuit_to_dag(circuit):
         else:
             control = (instruction.control[0], instruction.control[1])
 
-        instruction = copy.deepcopy(instruction)
-        dagcircuit.apply_operation_back(instruction, qargs, cargs, control)
+        def duplicate_instruction(inst):
+            """Create a fresh instruction from an input instruction."""
+            if issubclass(inst.__class__,
+                          inst_mod.Instruction) and inst.__class__ not in [
+                              inst_mod.Instruction, gate.Gate]:
+                if inst.name == 'barrier':
+                    new_inst = inst.__class__(inst.num_qubits)
+                elif inst.name == 'initialize':
+                    params = getattr(inst, 'params', [])
+                    new_inst = inst.__class__(params)
+                elif inst.name == 'snapshot':
+                    label = inst.params[0]
+                    snap_type = inst.params[1]
+                    new_inst = inst.__class__(inst.num_qubits, inst.num_clbits,
+                                              label, snap_type)
+                else:
+                    params = getattr(inst, 'params', [])
+                    new_inst = inst.__class__(*params)
+            else:
+                if isinstance(inst, gate.Gate):
+                    new_inst = gate.Gate(inst.name, inst.num_qubits,
+                                         inst.params)
+                else:
+                    new_inst = inst_mod.Instruction(name=inst.name,
+                                                    num_qubits=inst.num_qubits,
+                                                    num_clbits=inst.num_clbits,
+                                                    params=inst.params)
+                new_inst.definition = inst.definition
+            return new_inst
+
+        dagcircuit.apply_operation_back(duplicate_instruction(instruction),
+                                        qargs, cargs, control)
 
     return dagcircuit
diff --git a/qiskit/converters/dag_to_circuit.py b/qiskit/converters/dag_to_circuit.py
--- a/qiskit/converters/dag_to_circuit.py
+++ b/qiskit/converters/dag_to_circuit.py
@@ -6,11 +6,12 @@
 # the LICENSE.txt file in the root directory of this source tree.
 
 """Helper function for converting a dag to a circuit"""
-import copy
 import collections
 
-from qiskit.circuit import QuantumCircuit
 from qiskit.circuit import ClassicalRegister
+from qiskit.circuit import Instruction
+from qiskit.circuit import Gate
+from qiskit.circuit import QuantumCircuit
 from qiskit.circuit import QuantumRegister
 
 
@@ -50,8 +51,38 @@ def dag_to_circuit(dag):
         else:
             control = (node.condition[0], node.condition[1])
 
-        inst = copy.deepcopy(node.op)
+        def duplicate_instruction(inst):
+            """Create a fresh instruction from an input instruction."""
+            if issubclass(inst.__class__,
+                          Instruction) and inst.__class__ not in [
+                              Instruction, Gate]:
+                if inst.name == 'barrier':
+                    new_inst = inst.__class__(inst.num_qubits)
+                elif inst.name == 'initialize':
+                    params = getattr(inst, 'params', [])
+                    new_inst = inst.__class__(params)
+                elif inst.name == 'snapshot':
+                    label = inst.params[0]
+                    snap_type = inst.params[1]
+                    new_inst = inst.__class__(inst.num_qubits,
+                                              inst.num_clbits,
+                                              label, snap_type)
+                else:
+                    params = getattr(inst, 'params', [])
+                    new_inst = inst.__class__(*params)
+            else:
+                if isinstance(inst, Gate):
+                    new_inst = Gate(inst.name, inst.num_qubits,
+                                    inst.params)
+                else:
+                    new_inst = Instruction(name=inst.name,
+                                           num_qubits=inst.num_qubits,
+                                           num_clbits=inst.num_clbits,
+                                           params=inst.params)
+                new_inst.definition = inst.definition
+            return new_inst
+
+        inst = duplicate_instruction(node.op)
         inst.control = control
         circuit.append(inst, qubits, clbits)
-
     return circuit


Test patch: