Jump to content

Connect Leeroopedia MCP: Equip your AI agents to search best practices, build plans, verify code, diagnose failures, and look up hyperparameter defaults.

Heuristic:Avhz RustQuant MC Parallel Path Threshold

From Leeroopedia



Knowledge Sources
Domains Optimization, Monte_Carlo
Last Updated 2026-02-07 19:00 GMT

Overview

Enable parallel path generation via rayon when simulating more than ~1000 Monte Carlo paths to overcome threading overhead.

Description

The RustQuant stochastic process simulation engine provides a `parallel` flag in `StochasticProcessConfig` that switches between serial `iter_mut()` and rayon's `par_iter_mut()` for path generation. Each path is independently simulated with its own RNG seeded via `base_seed.wrapping_add(i)`, making parallelization embarrassingly parallel. However, rayon thread pool initialization and work-stealing overhead means that for small path counts, serial execution is faster.

Usage

Apply this heuristic when configuring Monte Carlo simulations via `StochasticProcessConfig`. Set `parallel = true` when `m_paths` exceeds approximately 1000. For fewer paths, keep `parallel = false` to avoid threading overhead.

The Insight (Rule of Thumb)

  • Action: Set `parallel = true` in `StochasticProcessConfig` when `m_paths > 1000`.
  • Value: Threshold of approximately 1000 paths.
  • Trade-off: Below ~1000 paths, serial execution is faster due to rayon thread pool overhead. Above ~1000 paths, parallel execution scales near-linearly with CPU cores.
  • Seed Determinism: Parallel execution remains deterministic when a seed is provided, as each path index generates its own seed via `wrapping_add`.

Reasoning

Rayon's work-stealing thread pool has fixed initialization cost and per-task scheduling overhead. For Monte Carlo simulation, each path is independent (embarrassingly parallel), so the speedup approaches the number of CPU cores for large path counts. The crossover point where parallelization overhead is amortized by the per-path computation cost is empirically around 1000 paths, depending on the number of time steps per path.

The codebase uses the same `scheme` closure for both serial and parallel execution, with only the iterator type changing:

Code Evidence

Parallel vs. serial branching from `simulation.rs:143-158`:

let base_seed: u64 = config.seed.unwrap_or_else(rand::random);
if config.parallel {
    paths.par_iter_mut().enumerate().for_each(|(i, path)| {
        let noise_gen = match fractional_config {
            Some(fractional_config) => NoiseGenerator::Fractional(fractional_config),
            None => NoiseGenerator::Dynamic(StdRng::seed_from_u64(base_seed.wrapping_add(i as u64))),
        };
        scheme(path, noise_gen);
    });
} else {
    paths.iter_mut().enumerate().for_each(|(i, path)| {
        let noise_gen = match fractional_config {
            Some(fractional_config) => NoiseGenerator::Fractional(fractional_config),
            None => NoiseGenerator::Dynamic(StdRng::seed_from_u64(base_seed.wrapping_add(i as u64))),
        };
        scheme(path, noise_gen);
    });
}

Time validation assertion from `simulation.rs:28`:

assert!(config.t_0 < config.t_n);

Related Pages

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment