Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/provablehq/snarkvm/llms.txt

Use this file to discover all available pages before exploring further.

The parallel execution module provides utilities and macros for conditional parallelization. Code written with these macros automatically adapts to be parallel or serial based on feature flags.

Feature-Based Parallelism

The parallel module behavior is controlled by the serial feature flag:
# Parallel execution (default)
[dependencies]
snarkvm-utilities = "*"

# Serial execution (no parallelism)
[dependencies]
snarkvm-utilities = { version = "*", features = ["serial"] }
When serial is NOT enabled:
  • Uses Rayon for parallel execution
  • Automatically utilizes multiple CPU cores
  • Best for production and performance-critical code
When serial IS enabled:
  • Falls back to standard sequential iterators
  • Single-threaded execution
  • Best for testing, debugging, and WebAssembly

Core Macros

cfg_iter!

Creates a parallel or serial iterator over references.
use snarkvm_utilities::cfg_iter;

let data = vec![1, 2, 3, 4, 5];

// Automatically parallel or serial based on features
let doubled: Vec<_> = cfg_iter!(data)
    .map(|x| x * 2)
    .collect();

assert_eq!(doubled, vec![2, 4, 6, 8, 10]);
Expands to:
// Without serial feature
data.par_iter().map(|x| x * 2).collect()

// With serial feature
data.iter().map(|x| x * 2).collect()

cfg_iter! with Minimum Length

Control the minimum chunk size for parallel execution:
use snarkvm_utilities::cfg_iter;

// Only parallelize if chunks are at least 100 items
let result: Vec<_> = cfg_iter!(large_data, 100)
    .map(|item| process(item))
    .collect();
This avoids parallelization overhead for small tasks.

cfg_iter_mut!

Creates a parallel or serial iterator over mutable references.
use snarkvm_utilities::cfg_iter_mut;

let mut data = vec![1, 2, 3, 4, 5];

// Mutate in parallel
cfg_iter_mut!(data).for_each(|x| *x *= 2);

assert_eq!(data, vec![2, 4, 6, 8, 10]);

cfg_into_iter!

Creates a parallel or serial consuming iterator.
use snarkvm_utilities::cfg_into_iter;

let data = vec![1, 2, 3, 4, 5];

// Consume and transform
let owned: Vec<_> = cfg_into_iter!(data)
    .map(|x| x * 2)
    .collect();

assert_eq!(owned, vec![2, 4, 6, 8, 10]);

cfg_chunks!

Iterates over fixed-size chunks.
use snarkvm_utilities::cfg_chunks;

let data = vec![1, 2, 3, 4, 5, 6, 7, 8];

// Process in chunks of 2
let sum_of_chunks: Vec<_> = cfg_chunks!(data, 2)
    .map(|chunk| chunk.iter().sum::<i32>())
    .collect();

assert_eq!(sum_of_chunks, vec![3, 7, 11, 15]);

cfg_chunks_mut!

Iterates over mutable fixed-size chunks.
use snarkvm_utilities::cfg_chunks_mut;

let mut data = vec![1, 2, 3, 4, 5, 6];

// Double each chunk
cfg_chunks_mut!(data, 2).for_each(|chunk| {
    for x in chunk {
        *x *= 2;
    }
});

assert_eq!(data, vec![2, 4, 6, 8, 10, 12]);

Collection Macros

cfg_keys!

Iterates over keys in a map.
use snarkvm_utilities::cfg_keys;
use indexmap::IndexMap;

let mut map = IndexMap::new();
map.insert("a", 1);
map.insert("b", 2);
map.insert("c", 3);

let keys: Vec<_> = cfg_keys!(map).cloned().collect();
assert!(keys.contains(&"a"));

cfg_values!

Iterates over values in a map.
use snarkvm_utilities::cfg_values;
use indexmap::IndexMap;

let mut map = IndexMap::new();
map.insert("a", 1);
map.insert("b", 2);
map.insert("c", 3);

let sum: i32 = cfg_values!(map).sum();
assert_eq!(sum, 6);

Reduction Macros

cfg_reduce!

Applies a reduction operation.
use snarkvm_utilities::cfg_reduce;

let data = vec![1, 2, 3, 4, 5];

// Sum all elements
let sum = cfg_reduce!(
    cfg_iter!(data),
    || 0,              // Identity function
    |acc, x| acc + x   // Reduction function
);

assert_eq!(sum, 15);

cfg_reduce_with!

Reduces with a binary operation (no identity needed).
use snarkvm_utilities::cfg_reduce_with;

let data = vec![1, 2, 3, 4, 5];

let result = cfg_reduce_with!(
    cfg_iter!(data),
    |a, b| a + b
);

assert_eq!(result, Some(15));
Returns Option because the collection might be empty.

Search Macros

cfg_find!

Finds an element matching a predicate.
use snarkvm_utilities::cfg_find;
use indexmap::IndexMap;

let mut map = IndexMap::new();
map.insert("a", 1);
map.insert("b", 2);
map.insert("c", 3);

let found = cfg_find!(map, |&x| x > 2);
assert_eq!(found, Some(&3));
Note: Returns at most one match, not necessarily the first in parallel mode.

cfg_find_map!

Finds and transforms an element.
use snarkvm_utilities::cfg_find_map;
use indexmap::IndexMap;

let mut map = IndexMap::new();
map.insert("a", 1);
map.insert("b", 2);
map.insert("c", 3);

let found = cfg_find_map!(map, |&x| {
    if x > 2 {
        Some(x * 10)
    } else {
        None
    }
});

assert_eq!(found, Some(30));

Sorting Macros

cfg_sort_unstable_by!

Sorts a slice using an unstable sort.
use snarkvm_utilities::cfg_sort_unstable_by;

let mut data = vec![3, 1, 4, 1, 5, 9, 2, 6];

cfg_sort_unstable_by!(data, |a, b| a.cmp(b));

assert_eq!(data, vec![1, 1, 2, 3, 4, 5, 6, 9]);

cfg_sort_by_cached_key!

Sorts using a cached key function.
use snarkvm_utilities::cfg_sort_by_cached_key;

let mut data = vec!["hello", "world", "foo", "bar"];

// Sort by length (key is cached)
cfg_sort_by_cached_key!(data, |s| s.len());

assert_eq!(data, vec!["foo", "bar", "hello", "world"]);

ExecutionPool

For dynamic job scheduling, use ExecutionPool:
use snarkvm_utilities::ExecutionPool;

let mut pool = ExecutionPool::new();

// Add jobs dynamically
for i in 0..10 {
    pool.add_job(move || i * i);
}

// Execute all jobs (potentially in parallel)
let results = pool.execute_all();

assert_eq!(results.len(), 10);
assert!(results.contains(&0));  // 0 * 0
assert!(results.contains(&81)); // 9 * 9

With Capacity

Pre-allocate for better performance:
use snarkvm_utilities::ExecutionPool;

let mut pool = ExecutionPool::with_capacity(100);

for i in 0..100 {
    pool.add_job(move || expensive_computation(i));
}

let results = pool.execute_all();

CPU Detection

The module detects CPU type to optimize thread usage.

max_available_threads()

Returns the optimal number of threads for the current CPU.
#[cfg(not(feature = "serial"))]
use snarkvm_utilities::max_available_threads;

let thread_count = max_available_threads();
println!("Using {} threads for parallel execution", thread_count);
CPU-specific behavior: Intel CPUs:
  • Returns physical core count
  • Avoids hyperthreading overhead
  • Better for CPU-intensive cryptographic operations
AMD CPUs:
  • Returns all available threads
  • Leverages simultaneous multithreading (SMT)
  • Better overall throughput
Unknown CPUs:
  • Returns all available threads
  • Safe default

execute_with_max_available_threads()

Executes a closure with optimal thread count.
#[cfg(not(feature = "serial"))]
use snarkvm_utilities::execute_with_max_available_threads;

let result = execute_with_max_available_threads(|| {
    // This closure runs in a thread pool
    expensive_parallel_computation()
});
Automatically creates a thread pool if not already in one.

Common Patterns

Parallel Map

use snarkvm_utilities::cfg_iter;

let data = vec![1, 2, 3, 4, 5];

let results: Vec<_> = cfg_iter!(data)
    .map(|x| x * x)
    .collect();

assert_eq!(results, vec![1, 4, 9, 16, 25]);

Parallel Filter

use snarkvm_utilities::cfg_iter;

let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

let evens: Vec<_> = cfg_iter!(data)
    .filter(|x| *x % 2 == 0)
    .cloned()
    .collect();

assert_eq!(evens, vec![2, 4, 6, 8, 10]);

Parallel Filter-Map

use snarkvm_utilities::cfg_iter;

let data = vec!["1", "2", "three", "4", "5"];

let numbers: Vec<_> = cfg_iter!(data)
    .filter_map(|s| s.parse::<i32>().ok())
    .collect();

assert_eq!(numbers, vec![1, 2, 4, 5]);

Parallel Sum

use snarkvm_utilities::cfg_iter;

let data = vec![1, 2, 3, 4, 5];

let sum: i32 = cfg_iter!(data).sum();
assert_eq!(sum, 15);

Parallel Validation

use snarkvm_utilities::cfg_iter;

let data = vec![1, 2, 3, 4, 5];

let all_positive = cfg_iter!(data).all(|x| *x > 0);
assert!(all_positive);

let any_negative = cfg_iter!(data).any(|x| *x < 0);
assert!(!any_negative);

Parallel Try-Map

use snarkvm_utilities::cfg_iter;

let data = vec!["1", "2", "3", "4", "5"];

let numbers: Result<Vec<_>, _> = cfg_iter!(data)
    .map(|s| s.parse::<i32>())
    .collect();

assert!(numbers.is_ok());

Performance Guidelines

When to Use Parallelism

Good candidates for parallelization:
  • Large datasets (>1000 items)
  • CPU-intensive operations per item
  • Independent computations (no shared state)
  • Cryptographic operations (hashing, signatures, proofs)
Poor candidates for parallelization:
  • Small datasets (<100 items)
  • I/O-bound operations
  • Operations with heavy synchronization
  • Very fast operations (overhead dominates)

Example: Choosing Serial vs Parallel

use snarkvm_utilities::cfg_iter;

// Small dataset: overhead may not be worth it
let small_data = vec![1, 2, 3, 4, 5];
let result: Vec<_> = small_data.iter().map(|x| x * 2).collect();

// Large dataset: parallelism helps
let large_data = vec![0; 1_000_000];
let result: Vec<_> = cfg_iter!(large_data)
    .map(|x| expensive_operation(x))
    .collect();

Minimum Length Tuning

Use minimum length to avoid over-parallelization:
use snarkvm_utilities::cfg_iter;

// Only create parallel tasks if chunks are at least 1000 items
let result: Vec<_> = cfg_iter!(data, 1000)
    .map(|item| process(item))
    .collect();

Testing with Serial Mode

For deterministic tests, enable serial mode:
cargo test --features serial
This ensures:
  • Deterministic execution order
  • Easier debugging
  • No race conditions in tests

WebAssembly Support

For WebAssembly targets, always use serial mode:
[target.'cfg(target_arch = "wasm32")'.dependencies]
snarkvm-utilities = { version = "*", features = ["serial", "wasm"] }
WebAssembly has limited threading support, so serial execution is required.

Example: Parallel Proof Verification

use snarkvm_utilities::cfg_iter;
use anyhow::Result;

fn verify_proofs_parallel(proofs: &[Proof]) -> Result<bool> {
    // Verify all proofs in parallel
    let results: Vec<_> = cfg_iter!(proofs)
        .map(|proof| proof.verify())
        .collect();
    
    // Check if all succeeded
    Ok(results.into_iter().all(|r| r.is_ok()))
}

// Usage
let proofs = generate_proofs();
let all_valid = verify_proofs_parallel(&proofs)?;
assert!(all_valid);

Example: Parallel Batch Processing

use snarkvm_utilities::{cfg_chunks, cfg_iter};

// Process 1 million items in batches of 1000
let data = vec![0; 1_000_000];

let results: Vec<_> = cfg_chunks!(data, 1000)
    .map(|chunk| {
        // Process each chunk
        chunk.iter().map(|x| process(*x)).sum::<u64>()
    })
    .collect();

assert_eq!(results.len(), 1000);

Example: Dynamic Job Scheduling

use snarkvm_utilities::ExecutionPool;

// Create pool
let mut pool = ExecutionPool::with_capacity(transactions.len());

// Add verification jobs
for tx in transactions {
    pool.add_job(move || verify_transaction(&tx));
}

// Execute all verifications (potentially in parallel)
let results = pool.execute_all();

// Check results
let all_valid = results.into_iter().all(|r| r);
assert!(all_valid);

Next Steps

Build docs developers (and LLMs) love