qprof: a gprof-inspired quantum profiler

We introduce qprof, a new and extensible quantum program profiler able to generate profiling reports of various quantum circuits. We describe the internal structure and working of qprof and provide three practical examples on practical quantum circuits with increasing complexity. This tool will allow researchers to visualise their quantum implementation in a different way and reliably localise the bottlenecks for efficient code optimisation.


I. INTRODUCTION
The quantum computing field has been evolving at an increasing rate in the past few years and is currently gaining more traction. Several quantum chips, the underlying hardware that enable researchers and companies to run quantum algorithms, have been announced by different research teams. The error rates and number of qubits provided by these chips greatly improved, with quantum hardware that have up to 65 qubits in early 2021.
Software has also seen a tremendous rise with the emergence of several quantum computing frameworks and languages such as Qiskit [2], Q# [29], PyQuil [7], Cirq [6] or myQLM [28] to name a few. These frameworks help in speeding-up the process of implementing a quantum algorithm by providing their own "standard library". Most of them also include specialised libraries whose purpose is to facilitate the development and testing of new quantum algorithms. For example, all the quantum computing frameworks cited previously include a library to simulate quantum circuits, some even implement several simulation algorithms such as a full state-vector simulator, a simulator for stabilizer circuits [1,15] or a simulator using matrix-product states as described in [25,30]. Most of the frameworks that target real quantum chips also include libraries to characterise a given quantum hardware, using for example randomised benchmarking [8,10,14,20,23] methods, or to mitigate hardware noise [4,21].
Finally, a large majority of the quantum computing frameworks provide a way to automatically optimise a quantum circuit. This optimisation is often performed during compilation, when the abstract quantum circuit representation is translated to be compliant with the targeted hardware. Automatic optimisation of quantum circuits is a broad area of research with algorithms based on pattern-matching [19,22,24], gate-based optimisation algorithms [3,13] or even pulse-based ones [9,16,26].
But even though automatic optimisation has already been shown to be successful in optimising complex quantum circuits [5], most algorithms only perform local op- * adrien.suau@lirmm.fr timisations without any knowledge about the algorithms used and do not have a global vision of the optimised circuit. This means that automatic optimisation algorithms will likely not be able to perform some optimisations that require a global vision of the quantum circuit or a semantic understanding of the quantum algorithms used. Identifying the usage of a non-optimal algorithm in the implementation and replacing it with a more efficient algorithm is an example of such an optimisation that cannot be performed without a semantic understanding of the implementation.
Currently, the only other way one has to optimise a given quantum implementation is "trial and error": try to locate a "hot spot" in the implementation, either by a tedious theoretical analysis or a manual counting of the routine calls, optimise the potential hot spot found and finally check if the optimisation performed improved the performances. This process has a severe drawback that makes it impractical on real-world implementations: the first step that consists in finding the hot spots is either imprecise or potentially very long, tedious and errorprone on large implementations. qprof aims at replacing this manual, tedious and errorprone step of analysis by automatically generating a report with all the useful information needed to find the hot spots of the given quantum program implementation. The qprof tool has been strongly inspired by classical profilers such as gprof [11,17] which try to solve the exact same issue but in classical (non-quantum) programming.
The rest of the paper is organised as follows. In Section II we explain the internals of qprof and explain in details its architecture, the design choices made, and their impact on the tool efficiency, extensibility and usability. We then include code snippets and simple practical examples in Section III to illustrate the tool usage.

II. HOW DOES QPROF WORKS?
A. General structure The general structure of qprof is composed of 3 main parts that interact with each other: framework plugins, core data structures and logic, and exporters.

arXiv:2106.07326v1 [quant-ph] 14 Jun 2021
The overall workflow of qprof is schematically explained in Figure 1. In this workflow, qprof can be seen as a black-box that takes a "quantum circuit" as input and returns a "profiler report". This black-box view should be enough for users that only want to use the qprof tool, but experienced users or plugins developers might need more details on the internals of qprof in order to understand how it works.
The following sections will introduce in details the three different parts that compose qprof. Section II B describes the plugins architecture, used by qprof to be as modular and extensible as possible. Section II C then shows how this plugin architecture is used to allow qprof to be mostly framework-agnostic by allowing anyone to implement a framework without modifying qprof code. A description of the core data structures and core logic is then provided in Section II D. Finally an explanation of the different exporters natively provided by qprof is given in Section II E.

B. Plugin architecture
The qprof tool aims at being the standard for profiling quantum circuits, independently of the framework they are written with. In order to be versatile and support as much current and future quantum computing frameworks as possible, qprof has been designed to be easily extensible to support new frameworks. qprof extensibility is achieved via a system of runtimediscovered plugins. In order to be discoverable by qprof, a plugin should naturally obey some rules such as implementing a specific interface (explained in Section II C) or being organised in an imposed way. But the key ingredient to extensiblity is that plugins do not have to be part of the main qprof tool: they can be developed and published by anyone. This allows several situations that may help improving qprof stability and evolution in time. For example, users might decide to roll-out their own plugin to support a new framework they are using internally. Another important situation that is made possible by this plugin architecture is that framework vendors have the opportunity to provide a qprof plugin along with their framework and to maintain it as an official plugin.
Finally, such an architecture based on external plugins allows the user to only install the plugins and frameworks needed instead of installing all the plugins along with qprof. This simple side improvement greatly reduces the installation time, installation size and plugin discovery time as it avoids installing and loading unused quantum computing frameworks. Observation 1. Even though the reduction in installation time, size and loading time may seem negligible at first sight, we found out that avoiding some packages adds a significant improvement. For example, we computed that installing qiskit 0.24.1 with Python 3.8.8 on a modest laptop (MacBook Air from 2017) with a very good Internet connection takes more than 1 minute and ends up using more than 600 Mo of disk space. About runtime effects, importing the main qiskit package from a Python interpreter can take up to several seconds.

C. Framework support
The goal of the qprof tool is to provide a unique profiling interface for quantum programs. As such, it should be able to support as much quantum computing programming frameworks as possible. Taking into account that several of the most successful quantum computing frameworks such as Qiskit, Cirq, PyQuil or myQLM are Python libraries, and in order to ease its integration with these already existing frameworks, qprof has naturally been designed as a Python library too.
But being written in the same language as a given quantum computing framework does not grant a direct compatibility. In fact, all the previously cited frameworks use a different representation for quantum programs. Qiskit implements a QuantumCircuit class, Cirq comes with a Circuit class, PyQuil uses a Program structure and finally myQLM uses either QRoutine or AbstractGate instances.
To be as independent as possible from the different quantum computing frameworks and their internal representation of a quantum circuit, qprof has been designed to be both extensible and generic.
Extensibility is achieved with the help of the plugin architecture described in Section II B. And in order to be as generic as possible, qprof uses an abstract common interface to represent the concept of "quantum (sub)routine". This concept is formally defined in Definitions 1 to 3.

Definition 1. Quantum routine:
a (possibly parametrised) named sequence of quantum subroutines. Definition 2. Quantum subroutine: a quantum routine that is part of a higher-level quantum routine (i.e. that is called by another quantum routine).
Definition 3. Native quantum subroutine: a quantum subroutine that represents a native hardware operation and that does not call any quantum subroutine.
Using Definitions 1 to 3, a common interface for the concept of "quantum routine" emerges. First, a quantum routine should have a name that can be retrieved. Secondly, we should be able to distinguish between native quantum subroutines and non-native ones. Finally, for non-native quantum subroutines, we need a way to iterate over all the subroutines composing it.
This interface, schematised in Figure 2, is the core abstraction layer of qprof that allows it to be as independent as possible from the underlying quantum computing framework used to represent the profiled quantum circuit. Each framework-specific representation is wrapped by a RoutineWrapper. Each supported framework should have a corresponding qprof plugin that implements the RoutineWrapper interface. The init method initialises a RoutineWrapper instance with an instance of the framework-specific quantum circuit representation. The name method returns the name of the currently wrapped routine. is base returns True if the routine is a native subroutine as defined in Definition 3, else it returns False. Finally, the iter method returns an object that can be iterated on and whose iterates are the different subroutines called by the current subroutine.

D. Core data structures and logic
Once the issue of adapting qprof to the various quantum computing frameworks has been solved, we can start considering the main problem of profiling a quantum circuit. The first important question to answer is: what kind of figures are interesting to include in a profile report? Section II D 1 tries to address this question as extensively as possible. Then, once the target quantity to measure is known, our main concern is: how to compute this quantity from the abstract quantum circuit representation presented in Section II C? qprof approach to solve this issue is presented in Section II D 2.

Interesting data to profile
Profiling a program is the action of gathering data on its execution. For classical programs and profilers, the list of data that can be gathered is quite extensive ranging from high-level quantities such as the time spent in a given function or the memory used during the program execution to low-level information recovered via hardware counters such as cache misses or branch-predictionmisses.
But for quantum computing, the quantities of interest need to be adapted as several classical data such as cachemiss or branch-prediction-miss do not have any meaning anymore. Nevertheless some classical quantities have a quantum analogue that may be useful for optimisation purposes. This is the case for the classical "instruction number" quantity, that translates trivially to its quantum counterpart "native gate number" (or "hardware gate number"). The number of native gates executed by a quantum routine is a useful information for several reasons: it is simple, the routine worst-case execution time can be computed from it and a lower-bound of the routine error rate can also be devised using this information.
Another classical quantity that can be translated to quantum computing is the "time spent in routine". This quantity can be subdivided in two more specific figures: the "time spent exclusively in routine" (sometimes called "self time") and the "time spent in subroutines called by the routine". This separation is often done in classical profiling programs as having these two execution times gives very useful information about the profiled routine that cannot be obtained from the "time spent in routine" only.
The last classical quantity with a meaningful quantum counterpart is the "memory usage", which may be translated as "number of qubits needed" when using quantum computers.
About quantities without a clear classical parallel but potentially useful, one can cite the "routine depth" as an approximation of the total execution time of the routine, the "T-count" for error-correction estimates or the "idle time" to estimate the potential effects of qubit decoherence on the routine.

Graph representation (call-graph)
Following Definitions 1 to 3 and the RoutineWrapper interface we defined in Section II C, a graph-like representation of a quantum program seems to be particularly well suited. In this representation, nodes are quantum subroutines and an oriented edge from node A to node B means that the quantum subroutine represented by A calls the quantum subroutine represented by B. This representation of a program is called a call-graph in classical computing.
Using Definitions 1 to 3 and the RoutineWrapper in- init , name, is base and iter methods are described in Figure 2. The eq method tests if other routine is equal to the current instance. hash computes an integer hash of the currently wrapper routine.
Grover's algorithm Call-graph representation of one possible implementation of Grover's algorithm. Each node represents one routine rather than one call to the routine. Edges have been regrouped and labelled with the number of calls for readability purposes. In the internal representation used by qprof, edges are not regrouped and are ordered to account for the original quantum program subroutine call order. terface, Figure 3 shows a representation of one possible Grover's algorithm implementation. Even though this representation is valid according to the general definition of a call-graph, it contains a lot of redundant information that scrambles the useful data in visual noise. Because of this, most of the call graphs representations avoid the duplication of nodes, i.e. create one node for a specific subroutine and re-use this node whenever the subroutine is called. And this brings to the main optimisation performed by qprof under-the-hood to keep its efficiency even on very large quantum circuits: routine caching. Instead of blindly performing a full exploration of the call-graph, qprof will keep in memory all the previously encountered subroutines and re-use the already computed data whenever possible. In order to make the cache efficient, qprof uses a hash-map structure. This means that in order to use the cache mechanism, RoutineWrapper instances should now be also hashable and comparable for equality. The final RoutineWrapper interface that includes these requirements is represented in Figure 4.
Using such an optimisation, the call-graph represented in Figure 3 changes to the one in Figure 5.
It is important to realise that this optimisation does BaseExporter init (**args) export(ProgramData) -> Any FIG. 6. Exporter interface. Any plugin that implements an exporter should be a derived from the BaseExporter class and implement this interface. The return type of the export method is not specified and can be anything. The main profile function will return the output of the exporter.
not delete any kind of information when compared to the non-optimised version. As noted in Figure 5, the order of subroutine calls is preserved and this optimisation simply allows to represent more efficiently the full call-graph.

E. Exporters
Along with framework support, the plugin architecture of qprof is also used to implement exporters that will transform the abstract quantum program representation described in Section II D 2 to a more usable format.
Just as the framework plugins, exporter plugins should implement a specific interface schematised in Figure 6. qprof natively implements two textual exporters: one that outputs a gprof-compatible format and another that returns a JSON-formatted string that directly represents a flat call-tree structure used internally by the qprof-compatible exporter.

Flat call-tree representation
Before the profiler report generation, it is convenient to summarise the information contained in the generic callgraph structure presented in Section II D 2. To do so, the gprof and JSON exporters both rely on a flat structure that represents a directed call-tree (i.e. a directed callgraph without loops).
This structure puts an additional restriction to the quantum programs that can be profiled using these exporters: the interdiction to have recursive subroutines (a subroutine that ends up calling itself). It is important to realise that this restriction does not have a huge impact on the area of application of qprof because, as of today, recursive subroutine calls are not widespread in the quantum computing programs and the restriction only applies to the gprof and JSON exporters, the core logic of qprof being capable of handling recursive subroutine calls.
The flat call-tree structure stores, for each subroutine A encountered in the call-graph exploration, a list of all the subroutines B called by A. Along with each called subroutine B, the structure stores the number of times B has been called by A and the execution time that was needed for these calls. Finally, in order to simplify the report generation, each called routine B will also store a list of the routines A it has been called by. Within this list is also stored the number of calls to B that have been performed from each A and the execution time of these calls.

gprof output
The gprof exporter aims at generating a profiler report that is compatible with the profiler report returned by gprof, a well-known classical profiler. Being compatible with a tool that has been around for decades and is still actively used has several advantages.
First and foremost, the fact that a tool that has been stable for decades and is still actively used shows that it provides satisfaction to its users, meaning that the output format includes enough information and is sufficiently easy to read and use in practice.
Secondly, a decades-old, largely used, output format is likely to have a lot of official or user-contributed tools to help analysing and representing it in the best way possible. This is the case for the gprof format that can be translated to a call-graph using the gprof2dot tool and the dot executable from the Graphviz library.
Finally, the gprof output is simple to generate: it is a textual file with a simple and regular format.

III. CODE EXAMPLES AND PRACTICAL APPLICATIONS
This section includes several examples of qprof usage on various quantum circuits ranging from a simple Toffoli gate decomposition in Section III A to more complex algorithm implementations such as Grover's algorithm in Section III B. All these benchmarks are performed on circuits generated using the qiskit framework. An example of benchmarking a quantum implementation of a 1dimensional wave equation solver written using the myqlm framework is finally provided in Section III C.

A. Benchmarking a simple program
One of the most simple quantum program that can be benchmarked is the implementation of a Toffoli gate. Such a benchmark has the benefit of being simple enough to be studied by hand which means that we will be able to verify qprof results by re-computing them.
The decomposition of a Toffoli gate as implemented in the qiskit framework is depicted in Figure 7. A com-plete example using qprof to profile the default Toffoli gate decomposition in qiskit is shown in Listing 1.
The output of qprof, which is here in a gprof format with some slight adaptations as discussed in Section II E, can then be analysed. For the sake of readability and brevity, the full gprof-compatible profiler report will not be included verbatim in this paper and will rather be visualised using the gprof2dot tool that allows representing gprof reports as call-graphs. The call-graph obtained from the report generated in Listing 1 is depicted in Figure 8.
From the call-graph depicted in Figure 8, it is clear that the cost of a Toffoli gate comes from its 6 controlled-X gates, that account for more than 98% of the total execution time. It is also interesting to note that the T gate, known to be very costly when error-correction is needed, is "free" on IBM chips when error-correction is not needed as it is equivalent to a phase change.

B. Grover's algorithm
The Toffoli gate is a good example to start and understand the meaning of qprof's output but the end goal of qprof is to be able to profile large and complex quantum circuits. A good first candidate to show how qprof performs on a more complex circuit is Grover's algorithm.
In this example we use Grover's algorithm on four qubits to find the three quantum states that verify the following formula: The only three 4-qubit quantum states verifying Equation (1) are |0001 , |1001 and |1101 , q 0 being the leftmost qubit in the bra-ket notation.
The code needed to generate the gprof-compatible output for Grover's algorithm with the oracle presented in Equation (1) is given in Listing 2. The resulting call-graph, included in Figure 9, clearly shows that the 100.00% 1× controlled-X gate is still the major contributor to execution time. But this time, contrarily to the Toffoli example shown in Section III A, the controlled-X gate is called by three different subroutines that all contribute significantly to the overall cost: c3z, ccz and mcx. Thanks to qprof it is now easy to understand the subroutines that contribute the most to the total execution time. More importantly, the qprof-compatible report and the call-graph representation gives very insightful information about subroutines calls that are crucial for circuit optimisation. Such information can be used to weight the impact of a given optimisation and then decide whether or not it is worth applying it.
For example, knowing that the ccz subroutine takes 18.61% of the total time, it is easy to deduce that a 20% improvement in the implementation of ccz will translate into a tiny 18.61% 5 = 3.72% improvement to the overall execution time, which might not be worth the effort. On the other hand, optimising the c3z subroutine to reduce its execution time by 20% improves the overall running time by 9.22%, which is nearly 10% and might be an interesting optimisation target. Finally, the call-graph visualisation conveys clearly the information that the cx gate is the most costly subroutine of the Grover's circuit, meaning that even a slight optimisation of the cx execution time will have a high impact on the overall implementation run-time.

C. Quantum wave equation solver
Finally, we include in this paper a more complex example that has been implemented in a previous work with myQLM, a quantum computing framework maintained by Atos. The code used to generate the benchmarked quantum program is available at https://gitlab.com/ cerfacs/qaths/ and is explained in [27].
This example demonstrates that, as can be seen in Figure 10, qprof interface stays nearly the same even though the framework used is now completely different. The only exceptions are some additional parameters (such as linking set in Figure 10) that are directly forwarded to the framework plugin used and additional gate definitions in the gate times data structure because of the way gate decomposition is handled in myQLM.
The call graph obtained by running Figure 10 is reproduced in Figure 11. In order for the call-graph to be readable on a paper format, some subroutines and calls (i.e. nodes and edges respectively) have been discarded from the graphical representation. The call-graph clearly shows that most of the execution time is spent in the oracle implementation. Moreover, (multi-)controlled-X gates are the major contributors to the total execution time. FIG. 9. Call-graph for the Grover's algorithm implementation. Quantum gates included in the gate times variable (here u, u1, u2, u3 and cx) are considered as native gates.
Only native gates have a non-zero self-time as they are the only gates that are really executed on the hardware. Some percentages might not add up to exactly 100% due to rounding errors.

IV. CONCLUSION
In this paper we introduced an open-source and, to the best of our knowledge, novel library that is able to generate profiling reports in well-known formats from a quantum circuit implementation. Our library is able to natively read quantum circuits from multiple frameworks -currently Qiskit and myQLM -and can be easily extended to support more quantum computing libraries. It generates consistent reports independently of the underlying framework used. qprof opens new optimisation opportunities for quantum scientists and programmers by allowing them to view their quantum circuit implementation in a well-known, familiar, synthetic and visual representation. In this paper, we first presented the main concepts used in the internals of qprof and required to make it as extendable as possible: the plugin interface used to wrap different quantum computing frameworks, the call-graph data structure and finally the different native exporters. Then, we used qprof on three different quantum circuit implementations of increasing complexity to demonstrate its features: simplicity of use, adaptability, from qaths.applications.wave_equation.evolve_1D_dirichlet import evolve_1d_dirichlet from qaths.applications.wave_equation.linking_sets.arithmetic_adder import get_linking_set as arith_linking_set from qprof import profile # Circuit generation time = 0.1 discretisation_size = 2 ** 10 epsilon = 1e-3 trotter_order = 1 routine = evolve_1d_dirichlet(time, discretisation_size, epsilon, trotter_order) # Gate execution time definition G = {"u1": 0, "u2": 89, "u3": 178, "cx": 930} gate_times = { "cu1": 2 * G["cx"] + 2 * G["u1"], "cu2": 2 * G["cx"] + 2 * G["u3"], "X": G consistency of the interface and efficiency.
In the future, we plan to extend the set of supported quantum computing frameworks. The number of exporters can also be improved to handle different output formats such as a perf event [12] compatible format, allowing to easily use new visualisations such as flame graphs [18].

SUPPLEMENTARY MATERIAL
The qprof tool is available at https://gitlab.com/ qcomputing/qprof/qprof. FIG. 11. Call-graph of the quantum wave equation solver. Nodes (i.e. quantum routines) that account for less than 0.5% of the total execution time are not plotted. Edges (i.e. subroutine calls) that account for 0.1% or less of the total execution time are also discarded for readability purposes.