Understanding Performance Metrics
Key Metrics
Circuit Size: Number of R1CS constraints in the compiled program- Directly proportional to proving time
- Each multiplication creates one constraint
- Target: Minimize constraints while maintaining functionality
- Scales roughly linearly with circuit size
- Can range from milliseconds to minutes
- Affected by: Circuit size, hardware (CPU, RAM)
- Scales with circuit size
- Large circuits may require 8GB+ RAM
- Out-of-memory errors occur with very large circuits
- Constant (~10ms) regardless of circuit size
- This is the power of zk-SNARKs!
Focus optimization efforts on circuit size as it directly impacts proving time and memory usage. Verification is already fast.
Automatic Compiler Optimizations
The Leo compiler applies several optimization passes automatically. Understanding these helps you write code that optimizes well.1. Constant Propagation
What It Does: Evaluates constant expressions at compile time. Example:- Use
constfor compile-time constants - Perform setup computations with constants when possible
- Let the compiler fold constants rather than pre-computing manually
2. Loop Unrolling
What It Does: Expands loops with constant bounds into sequential statements. Example:3. Dead Code Elimination (DCE)
What It Does: Removes unused variables and computations. Example:leo-compiler/src/compiler.rs:244-246:
4. Common Subexpression Elimination (CSE)
What It Does: Reuses computed values instead of recomputing. Example:5. Function Inlining
What It Does: Replacesinline function calls with the function body.
Example:
- Pro: Eliminates function call overhead, enables further optimization
- Con: Can increase code size if function is called many times
6. Static Single Assignment (SSA)
What It Does: Transforms code so each variable is assigned exactly once. Why It Matters: Enables aggressive optimization by making data flow explicit. Example:The compiler applies SSA automatically multiple times during compilation (
leo-compiler/src/compiler.rs:221-240). You don’t need to write in SSA form, but understanding it helps explain optimization behavior.Manual Optimization Techniques
Minimize Multiplications
Each multiplication creates one R1CS constraint. Additions are free.Choose the Right Data Structures
Arrays vs Repeated Variables
- Static index: Free (compiler resolves at compile time)
- Dynamic index: O(n) constraints where n = array size
Structs vs Tuples
Both have similar performance, choose for readability:Optimize Cryptographic Operations
Hash Function Selection
Commitment Schemes
Minimize Branching Overhead
In zero-knowledge circuits, both branches of a conditional are evaluated.Batch Operations
When possible, batch similar operations together:Avoid Redundant Validations
Hoist Loop-Invariant Code
Move computations that don’t change between iterations outside the loop:The compiler doesn’t automatically hoist loop-invariant code (loops are unrolled first). You must manually move invariant computations outside loops.
Advanced Optimization Patterns
Precomputed Tables
For expensive operations on small domains, use lookup tables:Lazy Evaluation
Defer expensive computations until necessary:- Making both branches efficient
- Ensuring one branch is trivial when possible
- Restructuring algorithms to avoid branching on expensive operations
Algebraic Optimizations
Use mathematical identities to reduce operations:Profiling and Measurement
Enable Compiler Statistics
The compiler tracks optimization effectiveness:Measure Circuit Size
Count the constraints in generated Aleo code:Benchmark Proving Time
Best Practices Checklist
Algorithm Design
- Use algorithms with minimal multiplications
- Avoid dynamic loops (use fixed iteration counts)
- Prefer iterative over recursive approaches
- Batch similar operations together
- Use lookup tables for small domains
Data Types
- Use appropriate integer sizes (u8 for small values, not u128)
- Prefer static array indexing over dynamic
- Use structs for clarity, tuples for brevity (equivalent performance)
- Avoid unnecessarily large arrays
Cryptography
- Use Poseidon2/Poseidon4/Poseidon8 for hashing
- Use BHP for commitments
- Avoid SHA-256 unless required for compatibility
- Batch cryptographic operations when possible
Control Flow
- Minimize conditional branches
- Make one branch trivial when possible
- Hoist loop-invariant code outside loops
- Unroll small loops (automatically done by compiler)
Code Organization
- Use
inlinefor small, frequently called functions - Use
constfor compile-time constants - Extract common subexpressions into variables
- Remove dead code and unused variables
Testing and Validation
- Profile before and after optimizations
- Measure circuit size (statement count)
- Benchmark proving time
- Verify correctness after each optimization
Common Optimization Mistakes
1. Premature Optimization
2. Over-Inlining
3. Ignoring Algorithmic Complexity
4. Unnecessary Precision
Optimization Workflow
- Implement: Write clear, correct code first
- Profile: Measure circuit size and proving time
- Identify: Find bottlenecks (expensive operations, large loops)
- Optimize: Apply targeted optimizations
- Measure: Verify improvement
- Repeat: Iterate until performance goals are met
Further Reading
- Compiler Architecture - How the compiler is structured
- Compiler Passes - What optimizations the compiler applies
- Zero-Knowledge Concepts - Understanding circuit constraints
- Operators - Language features and their costs