Skip to content

Zk proving optimization implementation #7

Description

@freesig

Implementation: Optimize risc0 zkVM Guest Code Performance

Technical Approach

Profile and optimize the state proof and recursive proof guest programs to reduce zkVM cycle counts through systematic measurement and zkVM-aware code optimization.

Implementation Details

1. Set Up Profiling Infrastructure

Add Cycle Count Instrumentation:

// In guest code
use risc0_zkvm::guest::env;

fn main() {
    let start = env::cycle_count();
    
    // Operation to profile
    expensive_operation();
    
    let end = env::cycle_count();
    eprintln!("expensive_operation cycles: {}", end - start);
}

Profiling Macro:

macro_rules! profile_section {
    ($name:expr, $code:block) => {{
        let start = env::cycle_count();
        let result = $code;
        let end = env::cycle_count();
        eprintln!("{}: {} cycles", $name, end - start);
        result
    }};
}

Files to Instrument:

guest-state-proof/src/main.rs      - Add cycle counting to all major sections
guest-recursive/src/main.rs        - Add cycle counting to all major sections

2. Baseline Measurements

Create Profiling Runs:

  • Run state proof guest with cycle counting enabled
  • Run recursive proof guest with cycle counting enabled
  • Document baseline cycle counts per section
  • Identify top 5 most expensive operations in each guest

Output Format:

=== State Proof Guest Baseline ===
Total cycles: 1,234,567
- Deserialization: 123,456 cycles (10%)
- State computation: 890,123 cycles (72%)
- Serialization: 98,765 cycles (8%)
- Other: 122,223 cycles (10%)

3. zkVM-Specific Optimizations

Common zkVM Performance Patterns:

Avoid Expensive Operations:

  • SHA-256 hashing: ~70K cycles per call
  • ECDSA signature verification: ~800K cycles
  • Large integer arithmetic: Variable, profile specific operations
  • Division/modulo: More expensive than multiplication
  • Slow serialization

Prefer Cheap Operations:

  • Bitwise operations: Generally cheap
  • Addition/subtraction: Cheap
  • Multiplication: Moderate cost
  • Simple comparisons: Cheap
  • Flat custom bytes serialization

Example Optimizations:

// BEFORE: Unnecessary allocations
let data = compute_data();
let serialized = serde_json::to_vec(&data)?;  // Allocation

// AFTER: Pre-allocate or use stack
let mut buffer = Vec::with_capacity(expected_size);
// Write directly to buffer

4. Proof Guest Optimizations

Profile Target Areas:

  • Input deserialization path
  • State transition computation
  • Merkle tree operations
  • Output serialization
  • State proof verification loop
  • Proof aggregation logic
  • Recursive proof construction
  • Final output generation

Optimize Serialization:

Minimize Copies:

// Use references and borrows aggressively
// Avoid clone() unless necessary
fn process_state(state: &State) {  // Borrow, don't take ownership
    // Process without copying
}

Reduce Guest-Host Communication:

// Minimize env::read() and env::commit() calls
// Read all inputs at once, commit all outputs at once
let all_inputs: Inputs = env::read();
// ... process ...
env::commit(&all_outputs);

5. Memory Optimization

Reduce Allocations:

  • Reuse buffers with .clear() instead of allocating new ones
  • Use fixed-size arrays on stack when possible
  • Profile memory with cycle counts around allocations

Example:

// BEFORE
for item in items {
    let buffer = Vec::new();  // Allocation per iteration
    process(item, &mut buffer);
}

// AFTER
let mut buffer = Vec::with_capacity(max_size);
for item in items {
    buffer.clear();  // Reuse buffer
    process(item, &mut buffer);
}

6. Algorithm Optimization

Replace O(n²) with O(n log n) where possible:

// BEFORE: Nested loops
for i in items {
    for j in items {
        if compare(i, j) { ... }
    }
}

// AFTER: Sort once, then process
items.sort_by_key(|item| item.key);
// Linear scan instead of quadratic

Early Exit Strategies:

// Add short-circuit conditions to avoid unnecessary work
if can_skip_expensive_check(&state) {
    return quick_path(state);
}
// Only do expensive work if necessary
expensive_computation(state)

8. Host vs Guest Profiling

**Host Flamegraph

# On host machine - use for general code structure understanding
cargo flamegraph --bin prove-state

Remember: Host flamegraph is misleading for guest optimization

  • Fast on host ≠ fast in zkVM
  • Slow on host ≠ slow in zkVM
  • Always validate with env::cycle_count() in guest

9. Common Pitfalls to Avoid

  • Don't optimize based on host flamegraphs alone
  • Don't assume standard Rust performance wisdom applies to zkVM
  • Don't optimize without measuring impact
  • Don't sacrifice correctness for speed

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions