The Noir profiler (Documentation Index
Fetch the complete documentation index at: https://mintlify.com/noir-lang/noir/llms.txt
Use this file to discover all available pages before exploring further.
noir-profiler) generates flamegraphs that map circuit costs back to the original source code. It supports three profiling modes:
| Mode | What it measures |
|---|---|
opcodes | ACIR opcode count per source location |
gates | Proving backend gate count per source location |
execution-opcodes | Brillig (unconstrained) opcode count per source location |
Installation
The profiler is installed automatically with Nargo starting fromnoirup v0.1.4.
Verify the installation:
noirup:
Quick circuit info with nargo info
Before reaching for the profiler,nargo info gives a fast overview of a compiled circuit’s opcode count without generating a flamegraph:
nargo info to track the overall size of a circuit as you make changes. Use the profiler when you need to understand which source locations are responsible for that size.
Profiling ACIR opcodes
ACIR opcodes are the intermediate representation consumed by proving backends. Profiling ACIR opcodes gives an approximate view of which parts of your program contribute most to circuit size and proving time.Example program
Create a new project and add the following source:Generating a flamegraph
./target/. Open it in a browser for an interactive view.
Flamegraph output: Each frame in the graph represents a source location. The width is proportional to the number of ACIR opcodes attributed to that location. Click on a frame to zoom in.
In the example above, 387 ACIR opcodes are generated. The flamegraph reveals that the majority come from the array[i] = 0 write inside the loop — a dynamic array write that requires memory opcodes for each element.
Searching the flamegraph
Click Search in the top-right corner and type a source expression (e.g.i > ptr). The matching frames are highlighted, and the Matched percentage in the bottom-right shows the fraction of total opcodes they account for.
Optimizing based on profiler results
After identifying a bottleneck, you can apply unconstrained functions to move expensive operations out of the constrained circuit. Before optimization: 387 ACIR opcodes — dynamic array writes dominate. After optimization: 284 ACIR opcodes — array writes moved to an unconstrained function, constrained circuit only checks results.Profiling proving backend gates
ACIR opcodes are only an approximation of proving cost. Different backends translate ACIR into different numbers of proving gates. Profile gates directly for an accurate picture.This feature requires a proving backend that supports the profiler’s gate profiling API. The example below uses Barretenberg (
bb).Generating a gates flamegraph
--backend-path— path to the proving backend binary. Ifbbis on yourPATH, usebbdirectly; otherwise provide the absolute path.- Arguments after
--are passed to the backend.
Interpreting gate counts
The gate flamegraph shows how the backend’s gate count is distributed across source locations. This distribution can differ significantly from the ACIR opcode flamegraph. For example, Barretenberg’s UltraHonk uses lookup tables for range checks, which carry a fixed setup cost regardless of circuit size. This meansblackbox::range may appear as a dominant contributor in small circuits but shrink to a small fraction in larger ones.
Example with array size 32: blackbox::range contributes most gates.
Example with array size 2048: blackbox::range contributes a much smaller percentage because the fixed setup cost is amortized over many more total gates.
This illustrates why it is useful to profile with different input sizes to understand how your circuit scales.
Profiling unconstrained execution
The profiler can also measure the Brillig (unconstrained VM) opcode count for programs that run entirely in unconstrained mode.Preparing the program
Add theunconstrained modifier to the main function from the earlier example:
Prover.toml with inputs:
Prover.toml:
Generating an execution flamegraph
Balancing proving and execution
Moving constrained operations to unconstrained functions reduces ACIR opcodes (and proving time) but increases Brillig opcodes (and execution time). The profiler lets you measure both sides of this tradeoff. For most Noir programs, proving time dominates, so reducing ACIR opcodes is typically the right priority. However, if execution time becomes a bottleneck, useexecution-opcodes profiling to identify where Brillig time is spent.
Workflow summary
Identify the dominant source locations
Use Search in the flamegraph to find specific expressions. Look for frames with the widest spans.
Apply optimizations
Common techniques:
- Move expensive array writes into
unconstrainedfunctions and assert the results. - Avoid dynamic array indexing where possible.
- Use compile-time constants instead of runtime inputs.
Re-profile to measure the improvement
Recompile and regenerate the flamegraph. Compare opcode counts before and after.